深入學習 es6 class

Rudy24發表於2019-07-21

es6 引入的 class 類實質上是 JavaScript 基於原型繼承的語法糖。

function Animal(name) {
  this.name = name;
}

Animal.prototype.sayHi = () => {
  return `Hello ${this.name}`;
};

// 等同於

class Animal {
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    return `Hello ${this.name}`;
  }
}
複製程式碼

類由兩部分組成:類宣告,類表示式

  1. 類宣告

class Animal {
  constructor(name) {
    this.name = name;
  }
}

複製程式碼

類實際上是個特殊的函式,普通函式宣告和類函式宣告有一個重要的區別就是函式 宣告會提升,而類宣告不會。如果先訪問,後宣告就會丟擲類似於下面的錯誤。


let animal = new Animal();
// Uncaught ReferenceError: Cannot access 'Animal' before initialization

class Animal {}
複製程式碼
  1. 類表示式

類表示式可以是被命名的或匿名的,(ps: 類表示式也同樣受到類宣告中提到的提升問題的困擾。)

// 匿名類
let Animal = class {
  constructor(name) {
    this.name = name;
  }
};

// 命名類
let Animal = class Cat {
  constructor(name) {
    this.name = name;
  }

  getClassName() {
    return Cat.name;
  }
};
複製程式碼

此時類名字Cat只能在 class 內部使用,指代當前類,在類的外部只能用Animal

  1. 建構函式 constructor方法是類的預設方法,通過new建立物件例項時,自動會呼叫該方法, 一個類必須擁有constructor方法,如果沒有寫,JavaScript 引擎會預設加上空的constructor方法。
class Animal {}

// 等同於

class Animal {
  constructor() {}
}
複製程式碼

constructor方法預設返回例項物件(既this),完全可以指定返回另外一個物件

class Animal {
  constructor() {
    return Object.create(null);
  }
}

new Animal() instanceof Animal; // false;
複製程式碼

上面程式碼中,constructor函式返回一個全新的物件,結果導致例項物件不是Animal類的例項。

  1. 嚴格模式 類和模組的內部,預設就是嚴格模式,比如,建構函式,靜態方法,原型方法,getter 和 setter 都在嚴格模式下執行。

  2. 類的例項 類的例項,通過 new 建立, 建立時會自動呼叫建構函式

class Animal {
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    return "My name is " + this.name;
  }
}

let animal = new Animal("rudy");
animal.sayHi(); // My name is rudy
複製程式碼
  1. 存取器 與 ES5 一樣,在類的內部可以使用getset關鍵字,對某個屬性設定存取 函式和取值函式,攔截該屬性的存取行為。
class Animal {
  constructor(name) {
    this.name = name;
  }

  get name() {
    return "rudy";
  }

  set name(value) {
    console.log("setter, " + this.value);
  }
}

let animal = new Animal("rudy");
animal.name = "Tom"; // setter, Tom
console.log(a.name); // rudy
複製程式碼
  1. 靜態方法 使用static修飾符修飾的方法稱為靜態,它們不需要例項化,直接通過類來呼叫。
class Animal {
  static sayHi(name) {
    console.log("i am " + name);
  }
}

let animal = new Animal();
Animal.sayHi("rudy"); // i am rudy
animal.sayHi("rudy"); // Uncaught TypeError: animal.sayHi is not a function
複製程式碼
  1. 例項屬性,靜態屬性 ES6 中的例項屬性只能通過建構函式中的this.xxx來定義,但最近 ES7 中可以直接在類裡面定義:
class Animal {
  name = "rudy";
  static value = 11;
  sayHi() {
    console.log(`hello, ${this.name}`);
  }
}

let animal = new Animal();
animal.sayHi(); // hello, rudy
Animal.value; // 11
animal.value; // undefiend
複製程式碼
  1. 類的繼承 使用extends關鍵字實現繼承,子類中使用super關鍵字來呼叫父類的建構函式和方法。
class Animal {
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    return "this is " + this.name;
  }
}

class Cat extends Animal {
  constructor(name, value) {
    super(name); // 呼叫父類的 constructor(name)
    this.value = value;
  }

  sayHi() {
    return `omg, ${super.sayHi()} it is ${this.value}`;
  }
}

let cat = new Cat("Tom", 11);
cat.sayHi(); // omg, this is Tom it is 11;
複製程式碼
  1. super 關鍵字 super這個關鍵字,既可以當著函式使用,也可以當著物件使用。兩種情況下,用法完全不同。

第一種情況,super作為函式呼叫時,代表父類的建構函式。ES6 要求,字類的建構函式必須執行一次super函式。

class Animal {}

class Cat extends Animal {
  constructor() {
    super();
  }
}
複製程式碼

上面程式碼中,子類Cat的建構函式中的super(),代表呼叫父類的建構函式,這是必須的,否在 JavaScript 引擎會報錯。 注意,super雖然代表了父類Animal的建構函式,但是返回的是字類Cat的例項,既super內部的this指的是Cat的例項,因此super()在這裡相當於 Animal.prototype.constructor.call(this)

class Animal {
  constructor() {
    console.log(new.target.name); // new.target指向當前正在執行的函式
  }
}

class Cat extends Animal {
  constructor() {
    super();
  }
}

new Animal(); // Animal;
new Cat(); // Cat;
複製程式碼

可以看出,在super()執行時,它指向的是子類Cat的建構函式,而不是父類Animal的建構函式,也就是說super內部的this指向是Cat

作為函式時,super()只能用在子類的建構函式之中,用在其他地方就會報錯。

class Animal {}

class Cat extends Animal {
  hi() {
    super(); // Uncaught SyntaxError: 'super' keyword unexpected here
  }
}
複製程式碼

第二種情況,super作為物件時:

  1. 在普通方法中,指向父類的原型物件,
  2. 在靜態方法中,指向父類。
class Animal {
  getName() {
    return "rudy";
  }
}

class Cat extends Animal {
  constructor() {
    super();
    console.log(super.getName());
  }
}

let cat = new Cat(); // rudy;
複製程式碼

上面程式碼中,子類Cat中的super.getName(),就是將super當作一個物件使用,這時,super在普通方法中,指向的是Animal.prototypesuper.getName()相當於Animal.prototype.getName()

這裡需要注意,由於super指向的是父類原型物件,所以定義在父類例項上的方法和屬性,是無法通過super獲取到的。

class Animal {
  constructor() {
    this.name = "rudy";
  }
}

class Cat extends Animal {
  constructor() {
    super();
  }
  getName() {
    return super.name;
  }
}

let cat = new Cat();
cat.getName(); // undefined;
複製程式碼

上面程式碼中,name是父類例項的屬性,而不是父類原型物件的屬性,所以super.name引用不到它。

用在靜態方法中,super將指向父類,而不是父類的原型物件。

class Animal {
  static getName(name) {
    console.log("static", name);
  }

  getName(name) {
    console.log("instance", name);
  }
}

class Cat extends Animal {
  constructor() {
    super();
  }

  static getName(name) {
    super.getName(name);
  }

  getName(name) {
    super.getName(name);
  }
}

Cat.getName("rudy"); // static rudy;

let cat = new Cat();
cat.getName("tom"); // instance tom;
複製程式碼

在上面程式碼中,super在靜態方法中指向父類,在普通方法中指向父類的原型物件。

另外,在字類的靜態方法中通過super呼叫父類的方法時,方法內部的this指向當前的子類,而不是子類例項。

class Animal {
  constructor() {
    this.name = "rudy";
  }

  static print() {
    console.log(this.name);
  }
}

class Cat extends Animal {
  constructor() {
    super();
    this.name = 2;
  }

  static print() {
    super.print();
  }
}

Cat.name = "Tom";
Cat.print(); // Tom;
複製程式碼

參考資料

  1. ES6
  2. TS class類

相關文章