Class繼承

Class 可以通過extends關鍵字實現繼承,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。

class Point {
}

class ColorPoint extends Point {
}

上面代碼定義了一個ColorPoint類,該類通過extends關鍵字,繼承了Point類的所有屬性和方法。但是由於沒有部署任何代碼,所以這兩個類完全一樣,等於複製了一個Point類。下面,我們在ColorPoint內部加上代碼。

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 調用父類的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 調用父類的toString()
  }
}

上面代碼中,constructor方法和toString方法之中,都出現了super關鍵字,它在這裏表示父類的構造函數,用來新建父類的this對象。

子類必須在constructor方法中調用super方法,否則新建實例時會報錯。這是因爲子類自己的this對象,必須先通過父類的構造函數完成塑造,得到與父類同樣的實例屬性和方法,然後再對其進行加工,加上子類自己的實例屬性和方法。如果不調用super方法,子類就得不到this對象。

父類的靜態方法,也會被子類繼承。

Object.getPrototypeOf方法可以用來從子類上獲取父類。

super這個關鍵字,既可以當作函數使用,也可以當作對象使用。在這兩種情況下,它的用法完全不同。

第一種情況,super作爲函數調用時,代表父類的構造函數。ES6 要求,子類的構造函數必須執行一次super函數。

class A {}

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

上面代碼中,子類B的構造函數之中的super(),代表調用父類的構造函數。這是必須的,否則 JavaScript 引擎會報錯。

注意,super雖然代表了父類A的構造函數,但是返回的是子類B的實例,即super內部的this指的是B,因此super()在這裏相當於A.prototype.constructor.call(this)

class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A() // A
new B() // B

上面代碼中,new.target指向當前正在執行的函數。可以看到,在super()執行時,它指向的是子類B的構造函數,而不是父類A的構造函數。也就是說,super()內部的this指向的是B

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

class A {}

class B extends A {
  m() {
    super(); // 報錯
  }
}

上面代碼中,super()用在B類的m方法之中,就會造成句法錯誤。

第二種情況,super作爲對象時,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類。

class A {
  p() {
    return 2;
  }
}

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

let b = new B();

上面代碼中,子類B當中的super.p(),就是將super當作一個對象使用。這時,super在普通方法之中,指向A.prototype,所以super.p()就相當於A.prototype.p()

這裏需要注意,由於super指向父類的原型對象,所以定義在父類實例上的方法或屬性,是無法通過super調用的。

class A {
  constructor() {
    this.p = 2;
  }
}

class B extends A {
  get m() {
    return super.p;
  }
}

let b = new B();
b.m // undefined

上面代碼中,p是父類A實例的屬性,super.p就引用不到它。

如果屬性定義在父類的原型對象上,super就可以取到。

class A {}
A.prototype.x = 2;

class B extends A {
  constructor() {
    super();
    console.log(super.x) // 2
  }
}

let b = new B();

上面代碼中,屬性x是定義在A.prototype上面的,所以super.x可以取到它的值。

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

class A {
  constructor() {
    this.x = 1;
  }
  print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  m() {
    super.print();
  }
}

let b = new B();
b.m() // 2

上面代碼中,super.print()雖然調用的是A.prototype.print(),但是A.prototype.print()內部的this指向子類B的實例,導致輸出的是2,而不是1。也就是說,實際上執行的是super.print.call(this)

由於this指向子類實例,所以如果通過super對某個屬性賦值,這時super就是this,賦值的屬性會變成子類實例的屬性。

class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(super.x); // undefined
    console.log(this.x); // 3
  }
}

let b = new B();

上面代碼中,super.x賦值爲3,這時等同於對this.x賦值爲3。而當讀取super.x的時候,讀的是A.prototype.x,所以返回undefined

如果super作爲對象,用在靜態方法之中,這時super將指向父類,而不是父類的原型對象。

class Parent {
  static myMethod(msg) {
    console.log('static', msg);
  }

  myMethod(msg) {
    console.log('instance', msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
    super.myMethod(msg);
  }

  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1); // static 1

var child = new Child();
child.myMethod(2); // instance 2

上面代碼中,super在靜態方法之中指向父類,在普通方法之中指向父類的原型對象。

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

class A {
  constructor() {
    this.x = 1;
  }
  static print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  static m() {
    super.print();
  }
}

B.x = 3;
B.m() // 3

上面代碼中,靜態方法B.m裏面,super.print指向父類的靜態方法。這個方法裏面的this指向的是B,而不是B的實例。

注意,使用super的時候,必須顯式指定是作爲函數、還是作爲對象使用,否則會報錯。

class A {}

class B extends A {
  constructor() {
    super();
    console.log(super); // 報錯
  }
}

上面代碼中,console.log(super)當中的super,無法看出是作爲函數使用,還是作爲對象使用,所以 JavaScript 引擎解析代碼的時候就會報錯。這時,如果能清晰地表明super的數據類型,就不會報錯。

class A {}

class B extends A {
  constructor() {
    super();
    console.log(super.valueOf() instanceof B); // true
  }
}

let b = new B();

上面代碼中,super.valueOf()表明super是一個對象,因此就不會報錯。同時,由於super使得this指向B的實例,所以super.valueOf()返回的是一個B的實例。

最後,由於對象總是繼承其他對象的,所以可以在任意一個對象中,使用super關鍵字。

var obj = {
  toString() {
    return "MyObject: " + super.toString();
  }
};

obj.toString(); // MyObject: [object Object]

Mixin 指的是多個對象合成一個新的對象,新對象具有各個組成成員的接口。它的最簡單實現如下。

const a = {
  a: 'a'
};
const b = {
  b: 'b'
};
const c = {...a, ...b}; // {a: 'a', b: 'b'}

上面代碼中,c對象是a對象和b對象的合成,具有兩者的接口。

下面是一個更完備的實現,將多個類的接口“混入”(mix in)另一個類。

function mix(...mixins) {
  class Mix {}

  for (let mixin of mixins) {
    copyProperties(Mix.prototype, mixin); // 拷貝實例屬性
    copyProperties(Mix.prototype, Reflect.getPrototypeOf(mixin)); // 拷貝原型屬性
  }

  return Mix;
}

function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== "constructor"
      && key !== "prototype"
      && key !== "name"
    ) {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}

上面代碼的mix函數,可以將多個對象合成爲一個類。使用的時候,只要繼承這個類即可。

class DistributedEdit extends mix(Loggable, Serializable) {
  // ...
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章