一、類
在js語言中,生成實例對象的傳統方法是通過構造函數。例如:
function Animal(type,name,age,sex){
this.type = type
this.name = name
this.age = age
this.sex = sex
}
Animal.prototype.print = function(){
console.log(`【種類】:${this.type}`)
console.log(`【名字】:${this.name}`)
console.log(`【年齡】:${this.age}`)
console.log(`【性別】:${this.sex}`)
}
const dog = new Animal("狗","巴頓",5,"公")
dog.print()
for (const key in dog) {
console.log(key)
}
爲了更好的,更簡潔的書寫類。ES6引入了Class類。通過class
關鍵字來定義。ES6的類只是構造函數的語法糖
1、類的簡介
通過類改造上面代碼
class Animal{
constructor(type,name,age,sex){
this.type = type
this.name = name
this.age = age
this.sex = sex
//constructor方法默認返回實例對象(即this),完全可以指定返回另外一個對象。
}
print(){
console.log(`【種類】:${this.type}`)
console.log(`【名字】:${this.name}`)
console.log(`【年齡】:${this.age}`)
console.log(`【性別】:${this.sex}`)
}
}
const dog = new Animal("狗","巴頓",5,"公")
dog.print()
for (const key in dog) {
console.log(key)
}
(1)constructor 方法
constructor方法是類的默認方法,通過
new命令生成對象實例時,自動調用該方法。一個類必須有
constructor方法,如果沒有顯式定義,一個空的
constructor`方法會被默認添加。
class Animal {
}
// 等同於
class Animal {
constructor() {}
}
(2)取值函數(getter)和存值函數(setter)
與 ES5 一樣,在“類”的內部可以使用get
和set
關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行爲。存值函數和取值函數是設置在屬性的 Descriptor 對象上的,不在原型上。
class Animal{
constructor(name,age){
this.name = name
this.age = age
}
// 創建一個age屬性,並給他加上setter,給該屬性賦值時,會運行該函數
set age(age){
if(age<0){
age = 0
}else if(age>150){
age = 100
}
this._age = age
}
// 創建一個age屬性,並給他加上getter,給該屬性賦值時,會運行該函數
get age(){
return this._age + "歲"
}
}
const dog = new Animal("巴頓",5)
console.log(dog.age)
相當於es5中寫法如下 Object.defineProperty
可定義某個對象成員屬性的讀取和設置
Object.defineProperty(this,"age",{
set(val){
//...
},
get(){
//...
}
})
(3)屬性表達式:可計算的成員名
const printName = "print"
class Animal{
constructor(type,name,age,sex){
this.type = type
this.name = name
this.age = age
this.sex = sex
}
[printName](){
console.log(`【種類】:${this.type}`)
console.log(`【名字】:${this.name}`)
console.log(`【年齡】:${this.age}`)
console.log(`【性別】:${this.sex}`)
}
}
const dog = new Animal("狗","巴頓",5,"公")
dog.print()
for (const key in dog) {
console.log(key)
}
(4)Class類表達式
const A = class { //匿名類 ,類表達式
a = 1
b = 2
}
const a = new A()
採用 Class 表達式,可以寫出立即執行的 Class。
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('ll');
person.sayName();
2、有關類的注意點
(1)類和模塊的內部,默認就是嚴格模式。類中的所有代碼均在嚴格模式下執行
(2)類聲明不會被提升,與 let
和 const
一樣,存在暫時性死區
new A(); // ReferenceError
class A {}
(3)類的所有方法都是不可枚舉的
(4)類的所有方法都無法被當作構造函數使用
(5)類的構造器必須使用 new
來調用
(6)類的方法內部如果含有this
,它默認指向類的實例。
class Person {
printName(name = '小張') {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const person = new Person();
const { printName } = person;
printName(); // TypeError: Cannot read property 'print' of undefined
原因:printName
方法中的this
,默認指向Person
類的實例。但是,如果將這個方法提取出來單獨使用,this
會指向該方法運行時所在的環境(由於 class
內部是嚴格模式,所以 this
實際指向的是undefined
),從而導致找不到print
方法而報錯。
3、靜態成員
如果在一個方法前,加上static
關鍵字,就表示該方法不會被實例繼承,而是直接通過類來調用構造函數本身的成員。使用static關鍵字定義的成員即靜態成員
class Chess{
//使用static關鍵字定義的
static width = 100
static height = 100
static getWidth(){
return `width:${Chess.width}`
}
constructor(name){
this.name = name
}
}
//與使用static關鍵字定義一樣,定義在構造函數身上
Chess.width = 100 //每個棋子都應該有寬高
Chess.getWidth()
注意:
(1)使用static
的字段初始化器,添加的是靜態成員
(2)沒有使用static
的字段初始化器,添加的成員位於對象上
class Chess{
static width = 100
static height = 100
step = 1 //相當於直接寫在constructor構造器中
constructor(name){
this.name = name
//this.step = 1 //上面代碼的效果
}
}
4、類的繼承
(1)類的繼承簡介
Class 可以通過extends
關鍵字實現繼承,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。
如果子類定義了constructor
構造器,則必須在constructor
的第一行通過super
關鍵字調用父類的構造方法。如果子類沒有定義constructor
,則會有默認的構造器,該構造器需要的參數和父類一致,並且自動調用父類構造器
class Animal{
constructor(type,name,age,sex){
this.type = type
this.name = name
this.age = age
this.sex = sex
}
static print(){
console.log(`【種類】:${this.type}`)
console.log(`【名字】:${this.name}`)
console.log(`【年齡】:${this.age}`)
console.log(`【性別】:${this.sex}`)
}
}
class Dog extends Animal{
constructor(name,age,sex,weight){
super("犬類",name,age,sex)//通過super關鍵字調用父類的構造器
this.weight = weight
}
print2(){
console.log(`【性別】:${this.weight}`)
}
}
const dog = new Animal("巴頓",5,"公",100)
dog.print()
注意:父類的靜態方法,也會被子類繼承。
(2)Object.getPrototypeOf()
可以從子類上獲取父類。因此,可以使用這個方法判斷,一個類是否繼承了另一個類。
Object.getPrototypeOf(Dog)//返回Dog的父類Animal
Object.getPrototypeOf(Dog) === Animal //true
(3)super 關鍵字
super
既可以當函數調用,也可以當對象使用。
a、super
作爲函數調用:
super
作爲函數調用時,代表父類的構造函數。ES6 要求,子類的構造函數必須執行一次super
函數。否則 JavaScript 引擎會報錯。
class A {}
class B extends A {
constructor() {
super();//調用A的構造器。
}
}
注意:super
內部的this仍指向B。只能用在子類的構造函數之中,用在其他地方就會報錯。
b、super
作爲對象調用:
class A {
print() {
console.log("我被調用了")
}
}
class B extends A {
constructor() {
super();
super.print(); // "我被調用了"
}
}
const b = new B();
子類B
當中的super.p()
,就是將super
當作一個對象使用。這時,super
在普通方法之中,指向A.prototype
,所以super.p()
就相當於A.prototype.p()
。
注意:由於super
指向父類的原型對象,所以定義在父類實例上的方法或屬性,是無法通過super
調用的。
二、符號Symbol
1、普通符號
符號是ES6新增的一個數據類型,它通過使用函數 Symbol(符號描述)
來創建。符號設計的初衷,是爲了給對象設置私有屬性(私有屬性:只能在對象內部使用,外面無法使用)
const sym1 = Symbol("這個是描述信息")
const sym2 = Symbol("abc")
console.log(sym1,sym2)//Symbol(這個是描述信息) Symbol(abc)
符號具有以下特點:
- 沒有字面量
- 使用 typeof 得到的類型是 symbol
- 每次調用 Symbol 函數得到的符號永遠不相等,無論符號名是否相同
- 符號可以作爲對象的屬性名存在,這種屬性稱之爲符號屬性
- 開發者可以通過精心的設計,讓這些屬性無法通過常規方式被外界訪問
- 符號屬性是不能枚舉的,因此在 for-in 循環中無法讀取到符號屬性,Object.keys 方法也無法讀取到符號屬性
- Object.getOwnPropertyNames 儘管可以得到所有無法枚舉的屬性,但是仍然無法讀取到符號屬性
- ES6 新增 Object.getOwnPropertySymbols 方法,可以讀取符號
const sym = Symbol("這是一個符號屬性")
const obj = {
a:1,
b:2,
[sym]:3
}
console.log(obj)//{a: 1, b: 2, Symbol(這是一個符號屬性): 3}
const syms = Object.getOwnPropertySymbols(obj)//獲取符號屬性
console.log(syms)//[Symbol(這是一個符號屬性)]
- 符號無法被隱式轉換,因此不能被用於數學運算、字符串拼接或其他隱式轉換的場景,但符號可以顯式的轉換爲字符串,通過 String 構造函數進行轉換即可,console.log 之所以可以輸出符號,是它在內部進行了顯式轉換
const hero = (function(){
const getRandom = Symbol("用於產生隨機數")
return{
attack:30,
hp:100,
defence:10,
battle(){
//攻擊,傷害:攻擊力*隨機數
const dmg = this.attack * this[getRandom](0.5,2)
console.log(dmg)
},
[getRandom](min,max){
return Math.random()* (max-min)+min
}
}
})()
console.log(hero)
console.log(hero.battle())//一個數
console.log(hero[getRandom](0.5,2))//Uncaught ReferenceError: getRandom is not defined
2、 共享符號
根據某個符號名稱(符號描述)能夠得到同一個符號
Symbol.for("符號名/符號描述") //獲取共享符號
const syb1 = Symbol.for("abc");
const syb2 = Symbol.for("abc");
console.log(syb1 === syb2)
const obj1 = {
a: 1,
b: 2,
[syb1]: 3
}
const obj2 = {
a: "a",
b: "b",
[syb2]: "c"
}
console.log(obj1, obj2);
3、 知名符號
知名符號是一些具有特殊含義的共享符號,通過 Symbol 的靜態屬性得到
ES6 延續了 ES5 的思想:減少魔法,暴露內部實現。因此,ES6 用知名符號暴露了某些場景的內部實現
(1)Symbol.hasInstance
該符號用於定義構造函數的靜態成員,它將影響 instanceof 的判定
obj instanceof A
//等效於
A[Symbol.hasInstance](obj)
//Function.prototype[Symbol.hasInstance]
function A() {}
Object.defineProperty(A, Symbol.hasInstance, {
value: function (obj) {
return false; //均返回false
}
})
const obj = new A();
console.log(obj instanceof A); //false
console.log(A[Symbol.hasInstance](obj)); //false
(2) Symbol.isConcatSpreadable:響數組的 concat 方法
該數組是否展開。默認是展開
const arr = [3];
const arr2 = [5, 6, 7, 8];
const result = arr.concat(56, arr2)
console.log(result)//[3, 56, 5, 6, 7, 8]
//-------------------
const arr = [3];
const arr2 = [5, 6, 7, 8];
arr2[Symbol.isConcatSpreadable] = false;
const result = arr.concat(56, arr2)
console.log(result)//[3, 56, [5,6,7,8]]
(3)Symbol.toPrimitive:影響類型轉換的結果
該對象被轉爲原始類型的值時,會調用這個方法,返回該對象對應的原始類型值。
class Temperature {
constructor(degree) {
this.degree = degree;
}
[Symbol.toPrimitive](type) {
if (type === "default") {
return this.degree + "攝氏度";
}
else if (type === "number") {
return this.degree;
}
else if (type === "string") {
return this.degree + "℃";
}
}
}
const t = new Temperature(30);
console.log(t + "!");//30攝氏度!
console.log(t / 2); //15
console.log(String(t));//30℃
(4)Symbol.toStringTag:影響 Object.prototype.toString 的返回值
class Person {
[Symbol.toStringTag] = "Person"
}
const p = new Person();
const arr = [32424, 45654, 32]
console.log(Object.prototype.toString.apply(p));//[object Person]
console.log(Object.prototype.toString.apply(arr))//[object Array]
(5)Symbol.iterator:可以改變遍歷器方法。
(6)Symbol.split:當該對象被String.prototype.split
方法調用時,會返回該方法的返回值
(7)Symbol.search:當該對象被String.prototype.search
方法調用時,會返回該方法的返回值。
(8)Symbol.replace:當該對象被String.prototype.replace
方法調用時,會返回該方法的返回值。
(9)Symbol.match:當執行str.match(myObject)
時,如果該屬性存在,會調用它,返回該方法的返回值
(10)Symbol.species:創建衍生對象時,會使用該屬性。
log(Object.prototype.toString.apply(p));//[object Person]
console.log(Object.prototype.toString.apply(arr))//[object Array]