1.簡介
ES6中,子類必須在constructor方法中調用super方法,否則新建實例時會報錯。這是因為子類自己的this對象,必須先通過父類的構造函數完成塑造,然后再加上子類自己的實例屬性和方法
ES5 的繼承,實質是先創造子類的實例對象this,然后再將父類的方法添加到this上面(Parent.apply(this)),它與ES6 的繼承機制完全不同
另一個需要注意的地方是,在子類的構造函數中,只有調用super之后,才可以使用this關鍵字,否則會報錯
1 class Point { 2 constructor(x, y) { 3 this.x = x; 4 this.y = y; 5 } 6 } 7 8 class ColorPoint extends Point { 9 constructor(x, y, color) { 10 this.color = color; // ReferenceError 11 super(x, y); 12 this.color = color; // 正確 13 } 14 }
2.Object.getPrototypeOf()
Object.getPrototypeOf方法可以用來從子類上獲取父類
1 Object.getPrototypeOf(ColorPoint) === Point 2 // true
因此,可以使用這個方法判斷,一個類是否繼承了另一個類
3.super 關鍵字
super這個關鍵字,既可以當作函數使用,也可以當作對象使用。在這兩種情況下,它的用法完全不同
第一種情況,super代表了父類A的構造函數,但是返回的是子類B的實例,即super內部的this指的是B的實例,因此super()在這里相當於A.prototype.constructor.call(this)
1 class A { 2 constructor() { 3 console.log(new.target.name); 4 } 5 } 6 class B extends A { 7 constructor() { 8 super(); 9 } 10 } 11 new A() // A 12 new B() // B
上面代碼中,new.target指向當前正在執行的函數。可以看到,在super()執行時,它指向的是子類B的構造函數,而不是父類A的構造函數
作為函數時,super()只能用在子類的構造函數之中,用在其他地方就會報錯
第二種情況,super作為對象時,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類
1 class A { 2 p() { 3 return 2; 4 } 5 } 6 class B extends A { 7 constructor() { 8 super(); 9 console.log(super.p()); // 2 10 } 11 } 12 13 let b = new B();
上面代碼中,子類B當中的super.p(),就是將super當作一個對象使用。這時,super在普通方法之中,指向A.prototype,所以super.p()就相當於A.prototype.p()
這里需要注意,由於super指向父類的原型對象,所以定義在父類實例上的方法或屬性,是無法通過super調用的
1 class A { 2 constructor() { 3 this.p = 2; 4 } 5 } 6 class B extends A { 7 get m() { 8 return super.p; 9 } 10 } 11 12 let b = new B(); 13 b.m // undefined
如果屬性定義在父類的原型對象上,super就可以取到
1 class A {} 2 A.prototype.x = 2; 3 4 class B extends A { 5 constructor() { 6 super(); 7 console.log(super.x) // 2 8 } 9 } 10 11 let b = new B();
另外,在子類的靜態方法中通過super調用父類的方法時,方法內部的this指向當前的子類,而不是子類的實例
注意,使用super的時候,必須顯式指定是作為函數、還是作為對象使用,否則會報錯
1 class A {} 2 3 class B extends A { 4 constructor() { 5 super(); 6 console.log(super.valueOf() instanceof B); // true 7 } 8 } 9 10 let b = new B();
上面代碼中,super.valueOf()表明super是一個對象,因此就不會報錯。同時,由於super使得this指向B的實例,所以super.valueOf()返回的是一個B的實例
最后,由於對象總是繼承其他對象的,所以可以在任意一個對象中,使用super關鍵字
4.類的 prototype 屬性和__proto__屬性
(1)子類的__proto__屬性,表示構造函數的繼承,總是指向父類。
(2)子類prototype屬性的__proto__屬性,表示方法的繼承,總是指向父類的prototype屬性
1 class A { 2 } 3 4 class B extends A { 5 } 6 7 B.__proto__ === A // true 8 B.prototype.__proto__ === A.prototype // true
這樣的結果是因為,類的繼承是按照下面的模式實現的
1 class A { 2 } 3 4 class B { 5 } 6 7 // B 的實例繼承 A 的實例 8 Object.setPrototypeOf(B.prototype, A.prototype); 9 10 // B 繼承 A 的靜態屬性 11 Object.setPrototypeOf(B, A); 12 13 const b = new B();
《對象的擴展》一章給出過Object.setPrototypeOf方法的實現
1 Object.setPrototypeOf = function (obj, proto) { 2 obj.__proto__ = proto; 3 return obj; 4 }
因此,就得到了上面的結果
1 Object.setPrototypeOf(B.prototype, A.prototype); 2 // 等同於 3 B.prototype.__proto__ = A.prototype; 4 5 Object.setPrototypeOf(B, A); 6 // 等同於 7 B.__proto__ = A;
這兩條繼承鏈,可以這樣理解:作為一個對象,子類(B)的原型(__proto__屬性)是父類(A);作為一個構造函數,子類(B)的原型對象(prototype屬性)是父類的原型對象(prototype屬性)的實例
子類實例的__proto__屬性的__proto__屬性,指向父類實例的__proto__屬性。也就是說,子類的原型的原型,是父類的原型
1 var p1 = new Point(2, 3); 2 var p2 = new ColorPoint(2, 3, 'red'); 3 4 p2.__proto__ === p1.__proto__ // false 5 p2.__proto__.__proto__ === p1.__proto__ // true
5.原生構造函數的繼承
原生構造函數是指語言內置的構造函數,通常用來生成數據結構。ECMAScript 的原生構造函數大致有下面這些
- Boolean()
- Number()
- String()
- Array()
- Date()
- Function()
- RegExp()
- Error()
- Object()
ES6 允許繼承原生構造函數定義子類,因為 ES6 是先新建父類的實例對象this,然后再用子類的構造函數修飾this,使得父類的所有行為都可以繼承。下面是一個繼承Array的例子
6.Mixin 模式的實現
Mixin 指的是多個對象合成一個新的對象,新對象具有各個組成成員的接口。它的最簡單實現如下
1 const a = { 2 a: 'a' 3 }; 4 const b = { 5 b: 'b' 6 }; 7 const c = {...a, ...b}; // {a: 'a', b: 'b'}
上面代碼中,c對象是a對象和b對象的合成,具有兩者的接口