ES6學習筆記篇三之類和符號

一、類

在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 一樣,在“類”的內部可以使用getset關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行爲。存值函數和取值函數是設置在屬性的 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)類聲明不會被提升,與 letconst一樣,存在暫時性死區

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]
(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:創建衍生對象時,會使用該屬性。
(11)Symbol.unscopables:該對象指定了使用with關鍵字時,哪些屬性會被with環境排除。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章