ES6 之 Class 的基本語法和繼承

Class 類基本用法

Class 類徹底能夠看做構造函數的另外一種寫法,類的數據類型其實就是函數,類自己也就是其實例的構造函數。使用的時候也是使用 new 命令。javascript

class Person {
  constructor(name,age) {
    this.name = name;
    this.age = age;
  }

  getName(){
    console.log(`My name is ${this.name}!`)
  }

  static sayHi() {
    console.log('Hi');
  }
}

let p = new Person();

typeof Person          // "function"
p instanceof Person    // true

constructor 方法

constructor 方法就是類的構造方法,this 關鍵字表明實例對象。其對應的也就是 ES5 的構造函數 Personjava

constructor 方法是類的默認方法,經過 new 命令生成對象實例時,會自動調用該方法。一個類必須有 constructor 方法,若是沒有顯式定義,會默認添加一個空的 constructor 方法。函數

class Person {}

Person.prototype    //  {construtor:f}

constructor 方法默認返回實例對象,也徹底能夠 return 另一個對象。this

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

new Foo() instanceof Foo      // false

類必須使用 new 調用,也就是 constructor 方法只能經過 new 命令執行,不然會報錯。prototype

class Foo {
  constructor(){} 
}

Foo()   // TypeError: Class constructor Foo cannot be invoked without 'new'

Class 的自定義方法

Class 類依舊存在 prototype 屬性,且類的全部方法都定義在 prototype 屬性上面。code

class Person {
  constructor() {}
  aaa(){}
  bbb(){}
}

Object.getOwnPropertyNames(Person.prototype)   // ["constructor", "aaa", "bbb"]

prototype 對象的 constructor 屬性,也是直接指向類自己。對象

Person.prototype.constructor === Person  // true
p.constructor === Person                 // true

類的新方法可經過 Object.assignprototype 一次性添加多個。繼承

class Person {
  constructor() {}
}

Object.assign(Person.prototype, {
  aaa(){},
  bbb(){}
})

注意,定義類的時候,前面不須要加上 function 關鍵字,也不須要逗號分隔。ip

與 ES5 構造函數的不一樣的是,Class 內部全部定義的方法,都是不可枚舉的。ci

class Person {
  constructor() {}
  aaa(){}
}

Object.keys(Person.prototype)                  // []
Object.getOwnPropertyNames(Person.prototype)   // ["constructor", "aaa"]

而 ES5 的構造函數的 prototype 原型定義的方法是可枚舉的。

let Person = function(){};
Person.prototype.aaa = function(){};

Object.keys(Person.prototype)                  // ["aaa"]
Object.getOwnPropertyNames(Person.prototype)   // ["constructor", "aaa"]

Class 類的屬性名,能夠採用表達式。

let methodName = 'getName';

class Person {
  constructor() {}
  [methodName](){}
}

Object.getOwnPropertyNames(Person.prototype)   // ["constructor", "getName"]

取值函數 getter 和存值函數 setter

與 ES5 同樣,在 Class 內部可使用 getset 關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行爲。

class Person {
  constructor() {
    this.name = 'dora';
  }
  get author() {
    return this.name;
  }
  set author(value) {
    this.name = this.name + value;
    console.log(this.name);
  }
}

let p = new Person();
p.author          //  dora
p.author = 666;   // dora666

且其中 author 屬性定義在 Person.prototype 上,但 getset 函數是設置在 author 屬性描述對象 Descriptor 上的。

Object.getOwnPropertyNames(Person.prototype)   // ["constructor", "author"]

Object.getOwnPropertyDescriptor(Person.prototype,'author')
// { get: ƒ author()(),
//   set: ƒ author()(value),
//   ...
// }

Class 的 static 靜態方法

類至關於實例的原型,全部在類中定義的方法,都會被實例繼承。但若是在一個方法前,加上 static 關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,這就稱爲「靜態方法」。

class Person {
  static sayHi() {
    console.log('Hi');
  }
}

Person.sayHi()      // "Hi"

let p = new Person();
p.sayHi()           // TypeError: p.sayHi is not a function

若是靜態方法包含 this 關鍵字,這個 this 指的是類,而不是實例。靜態方法能夠與非靜態方法重名。

class Person {
  static sayHi() {
    this.hi();
  }

  static hi(){
    console.log('hello')
  }

  hi(){
    console.log('world')
  }
}

Person.sayHi()      // "hello"

實例屬性的另外一種寫法

實例屬性除了定義在 constructor() 方法裏面的 this 上面,也能夠定義在類的最頂層。此時定義的時候,屬性前面不須要加上 this。而在類內部其它地方調用的時候,須要加上 this

class Person {
  name = 'dora';
  getName() {
    return this.name;
  }
}

let p = new Person();
p.name           // "dora"
Object.keys(p)   // ["name"]

這種寫法的好處是,全部實例對象自身的屬性都定義在類的頭部,看上去比較整齊,寫法簡潔,一眼就能看出這個類有哪些實例屬性。

Class 的繼承

Class 子類能夠經過 extends 關鍵字實現繼承。

class Person {
  constructor() {}
  sayHi() {
    return 'Hi';
  }
}

class Teacher extends Person {
  constructor() {
    super();
  }
}

let t = new Teacher();
t.sayHi();   // "Hi"

子類的 constructor

子類必須在 constructor 方法中調用 super() 方法,不然新建實例時會報錯。

若是子類沒有定義 constructor 方法,這個方法會被默認添加,且子類默認添加的 constructor 方法都會默認執行 super() 方法。

class Teacher extends Person {
}

let t = new Teacher();
t.sayHi();   // "Hi"

等同於

class Teacher extends Person {
  constructor(...args) {
    super(...args);
  }
}

super 關鍵字

super 這個關鍵字,既能夠看成函數使用,也能夠看成對象使用。用法徹底不一樣。

super() 方法

super 做爲函數調用時,表明父類的構造函數。子類的構造函數必須執行一次 super() 方法。

由於 ES6 的繼承機制與 ES5 構造函數不一樣,ES6 的子類實例對象 this 必須先經過父類的構造函數建立,獲得與父類一樣的實例屬性和方法後再添加子類本身的實例屬性和方法。所以若是不調用 super() 方法,子類就得不到 this 對象。

super 雖然表明了父類的構造函數,但返回的是子類的實例,即經過super 執行父類構造函數時,this 指的都是子類的實例。也就是 super() 至關於 Person.call(this)

class A {
  constructor() {
    console.log(this.constructor.name)
  }
}

class B extends A {
  constructor() {
    super();
  }
}

new A()       // A
new B()       // B

做爲函數時,super() 只能在子類的構造函數之中,用在其餘地方就會報錯。

super 對象

在普通方法中指向父類的 prototype 原型

super 做爲對象時,在普通方法中,指向父類的 prototype 原型,所以不在原型 prototype 上的屬性和方法不能夠經過 super 調用。

class A {
  constructor() {
    this.a = 3;
  }
  p() {return 2;}
}
A.prototype.m = 6;

class B extends A {
  constructor() {
    super();
    console.log(super.a);    // undefined
    console.log(super.p());  // 2
    console.log(super.m);    // 6
  }
}

new B();

在子類普通方法中經過 super 調用父類方法時,方法內部的 this 指向當前的子類實例。

class A {
  constructor() {
    this.x = 'a';
  }
  aX() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 'b';
  }
  bX() {
    super.aX();
  }
}

(new B()).bX()    // 'b'

在靜態方法中,指向父類

class A {
 static m(msg) {
   console.log('static', msg);
 }
 m(msg) {
   console.log('instance', msg);
 }
}

class B extends A {
  static m(msg) {
    super.m(msg);
  }
  m(msg) {
    super.m(msg);
  }
}

B.m(1);          // "static" 1
(new B()).m(2)   // "instance" 2

在子類靜態方法中經過 super 調用父類方法時,方法內部的 this 指向當前的子類,而不是子類的實例。

任意對象的 super

因爲對象老是繼承其它對象的,因此能夠在任意一個對象中,使用 super 關鍵字,指向的是該對象的構造函數的 prototype 原型。

let obj = {
  m() {
    return super.constructor.name;
  }
};
obj.m();    // Object

注意,使用 super 的時候,必須顯式的指定是做爲函數仍是做爲對象使用,不然會報錯。

class B extends A {
  m() {
    console.log(super);
  }
}
// SyntaxError: 'super' keyword unexpected here

靜態方法的繼承

父類的靜態方法,能夠被子類繼承。

class Person {
  static sayHi() {
    return 'hello';
  }
}

class Teacher extends Person {
}

Teacher.sayHi()      // "hello"

在子類的 static 內部,能夠從 super 對象上調用父類的靜態方法。

class Teacher extends Person {
  static sayHi() {
    super.sayHi();
  }
}

Teacher.sayHi()      // "hello"

new.target 屬性

Class 內部調用 new.target,返回當前 Class。且子類繼承父類時,new.target 會返回子類。所以利用這個特色,能夠寫出不能獨立使用必須繼承後才能使用的類。

class Shape {
  constructor() {
    if(new.target === Shape) {
      throw new Error('本類不能實例化');
    }
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    console.log('ok');
  }
}

let s = new Shape();      // 報錯
let cir = new Circle(6);  // 'ok'
相關文章
相關標籤/搜索