JS6-Class的繼承
====繼承
Class 可以通過extends關鍵字實現繼承
class Point {
}
class ColorPoint extends Point {
}
上面代碼定義了一個ColorPoint類,該類通過extends關鍵字,繼承了Point類的所有屬性和方法。但是由於沒有部署任何代碼,所以這兩個類完全一樣,等於復制了一個Point類。下面,我們在ColorPoint內部加上代碼。
class Point{
constructor(x,y){
this.x=x
this.y=y
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 調用父類的constructor(x, y)
this.color = color;
}
toString() {
return this.color
}
}
上面代碼中,constructor方法之中,都出現了super關鍵字,它在這裏表示父類的構造函數,用來新建父類的this對象。
子類必須在constructor方法中調用super方法,否則新建實例時會報錯。這是因為子類沒有自己的this對象,而是繼承父類的this對象,然後對其進行加工。如果不調用super方法,子類就得不到this對象。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
}
}
let cp = new ColorPoint(); // ReferenceError
ES6 的繼承機制實質是先創造父類的實例對象this(所以必須先調用super方法),然後再用子類的構造函數修改this。
如果子類沒有定義constructor方法,這個方法會被默認添加,代碼如下。也就是說,不管有沒有顯式定義,任何一個子類都有constructor方法。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
/*隱式的有:
constructor(x, y) {
Super(x,y)
this.x = x;
this.y = y;
}
*/
Myf(){console.log(this.x)}
}
在子類的構造函數中,只有調用super之後,才可以使用this關鍵字,否則會報錯。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正確
}
}
====super 關鍵字
super作為函數調用時,代表父類的構造函數。ES6 要求,子類的構造函數必須執行一次super函數。
class A {}
class B extends A {
constructor() {
super();
}
}
上面代碼中,子類B的構造函數之中的super(),代表調用父類的構造函數。這是必須的,否則 JavaScript 引擎會報錯。
super()只能用在子類的構造函數之中,用在其他地方就會報錯。
class A {}
class B extends A {
m() {
super(); // 報錯
}
}
類的 prototype 屬性和__proto__屬性
大多數瀏覽器的 ES5 實現之中,每一個對象都有__proto__屬性,指向對應的構造函數的prototype屬性。Class 作為構造函數的語法糖,同時有prototype屬性和__proto__屬性
(1)子類的__proto__屬性,表示構造函數的繼承,總是指向父類。
(2)子類prototype屬性的__proto__屬性,表示方法的繼承,總是指向父類的prototype屬性。
class A {
}
class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
可以這樣理解:作為一個對象,子類(B)的原型(__proto__屬性)是父類(A);作為一個構造函數,子類(B)的原型(prototype屬性)是父類的實例。
實例的 __proto__ 屬性
子類實例的__proto__屬性的__proto__屬性,指向父類實例的__proto__屬性。也就是說,子類的原型的原型,是父類的原型。
var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, ‘red‘);
p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true
ColorPoint繼承了Point,導致前者原型的原型是後者的原型。
因此,通過子類實例的__proto__.__proto__屬性,可以修改父類實例的行為。
p2.__proto__.__proto__.printName = function () {
console.log(‘Ha‘);
};
p1.printName() // "Ha"
上面代碼在ColorPoint的實例p2上向Point類添加方法,結果影響到了Point的實例p1。
原生構造函數的繼承
原生構造函數是指語言內置的構造函數,通常用來生成數據結構。
ES6 允許繼承原生構造函數定義子類,因為 ES6 是先新建父類的實例對象this,然後再用子類的構造函數修飾this,使得父類的所有行為都可以繼承。下面是一個繼承Array的例子。
class MyArray extends Array {
constructor(...args) {
super(...args);
}
}
var arr = new MyArray();
arr[0] = 12;
arr.length // 1
上面代碼定義了一個MyArray類,繼承了Array構造函數,因此就可以從MyArray生成數組的實例。這意味著,ES6 可以自定義原生數據結構(比如Array、String等)的子類,這是 ES5 無法做到的。
JS6-Class的繼承