es6中用class和extends關(guān)鍵字來實(shí)現(xiàn)繼承。ES6中引入了class關(guān)鍵字來聲明類, 而class(類)可通過extends關(guān)鍵字實(shí)現(xiàn)繼承,讓子類繼承父類的屬性和方法,語法“class 父類名{…} class 子類名 extends 父類名{…};”。
本教程操作環(huán)境:windows7系統(tǒng)、ECMAScript 6版、Dell G3電腦。
es6中可利用class關(guān)鍵字配合extends關(guān)鍵字來實(shí)現(xiàn)繼承。
ES6 Class 的繼承
1.簡介
Class可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承,讓子類繼承父類的屬性和方法。這比 ES5 的通過修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多。
//父類 class Point { ... } //子類 class ColorPoint extends Point { constructor(x, y, color) { super(x, y); this.color = color; } toString() { return this.color + '' + super.toString(); // 調(diào)用父類的toString方法 } }
上面代碼中,constructor方法和toString方法內(nèi)部,都出現(xiàn)了super關(guān)鍵字,super在這里表示父類的構(gòu)造函數(shù),用來新建一個(gè)父類的實(shí)例對(duì)象。
ES6規(guī)定,子類必須在constructor方法中調(diào)用super(),否則會(huì)報(bào)錯(cuò),這是因?yàn)樽宇愖约旱膖his對(duì)象,必須先通過父類的構(gòu)造函數(shù)完成塑造,得到與父類同樣的實(shí)例屬性和方法,然后在添加子類自己的實(shí)例屬性和方法。
為什么子類的構(gòu)造函數(shù),一定要調(diào)用super()?
這是因?yàn)樵贓S5的繼承機(jī)制中,是先創(chuàng)造一個(gè)獨(dú)立的子類的實(shí)例對(duì)象,然后再將父類的方法添加到這個(gè)對(duì)象上,即“實(shí)例在前,繼承在后”;ES6的繼承機(jī)制,則是先將父類的屬性和方法,加到一個(gè)空的對(duì)象上面,然后再將該對(duì)象作為子類的實(shí)例,即“繼承在前,實(shí)例在后”。
這意味著,每次新建子類實(shí)例時(shí),父類的構(gòu)造函數(shù)必定會(huì)先運(yùn)行一次
class Foo { constructor() { console.log(1); } } class Bar extends Foo { constructor() { super(); console.log(2); } } const bar = new Bar(); // 1 2
上面的代碼中,子類Bar新建實(shí)例時(shí),會(huì)輸出1和2,這就是因子類構(gòu)造函數(shù)調(diào)用super()時(shí),會(huì)執(zhí)行一次父類構(gòu)造函數(shù)。只有在子類的構(gòu)造函數(shù)中調(diào)用super之后,才可以使用this關(guān)鍵字,否則會(huì)報(bào)錯(cuò)。這是因?yàn)樽宇悓?shí)例的構(gòu)建,必須先完成父類的繼承,只有super方法才能讓子類實(shí)例繼承父類。
class Point { constructor(x, y) { this.x = x; this.y = y; } } class ColorPoint extends Point { constructor(x, y, color) { this.color = color; super(x, y); this.color = color; } }"
如果子類沒有定義constructor方法,這個(gè)方法會(huì)默認(rèn)添加,并且里面會(huì)調(diào)用super,也就是說,不管有沒有顯示定義,任何一個(gè)子類都有constructor方法.
class Point { constructor(x, y) { this.x = x; this.y = y; } } class ColorPoint extends Point { } let cp = new ColorPoint(25, 8); console.log(cp); //{x: 25, y: 8} class ColorPoint extends Point { constructor(...args) { super(...args); } } let cp = new ColorPoint(25, 8); console.log(cp); //{x: 25, y: 8}
2.私有屬性和私有方法的繼承
父類所有的屬性和方法,都會(huì)被子類繼承,除了私有的屬性和方法。子類無法繼承父類的私有屬性,或者說私有屬性只能在定義它的class里面使用。
class Foo { #p = 1; #m() { console.log('hello'); } } class Bar extends Foo { constructor() { super(); console.log(this.#p); // 報(bào)錯(cuò) this.#m(); // 報(bào)錯(cuò) } }
上面示例中,子類 Bar 調(diào)用父類 Foo 的私有屬性或私有方法,都會(huì)報(bào)錯(cuò)。
如果父類定義了私有屬性的讀寫方法,子類就可以通過這些方法,讀寫私有屬性。
class Foo { #p = 1; getP() { return this.#p; } } class Bar extends Foo { constructor() { super(); console.log(this.getP()); // 1 } }
3.靜態(tài)屬性和方法的繼承
父類的靜態(tài)屬性和靜態(tài)方法,也會(huì)被子類繼承。
class A { static hello() { console.log('hello world'); } } class B extends A { } B.hello() // hello world
上面代碼中,hello()
是A
類的靜態(tài)方法,B
繼承A
,也繼承了A
的靜態(tài)方法。
注意,靜態(tài)屬性是通過淺拷貝實(shí)現(xiàn)繼承的,如果繼承的屬性是原始數(shù)據(jù)類型,子類中操作繼承的靜態(tài)屬性不會(huì)影響到父類,但如果繼承的屬性是一個(gè)對(duì)象,那么子類修改這個(gè)屬性會(huì)印象到父類
class C { static foo = 100; } class D extends C { constructor() { super(); D.foo--; } } const d = new D(); C.foo; // 100 D.foo; // 99 class A { static foo = { n: 100 }; } class B extends A { constructor() { super(); B.foo.n--; } } const b = new B(); B.foo.n // 99 A.foo.n // 99
4.Object.getPrototypeOf()
Object.getPrototypeOf()
方法可以用來從子類上獲取父類。
class Point { /*...*/ } class ColorPoint extends Point { /*...*/ } Object.getPrototypeOf(ColorPoint) === Point // true
因此,可以使用這個(gè)方法判斷,一個(gè)類是否繼承了另一個(gè)類。
5.super關(guān)鍵字
super關(guān)鍵字既可以當(dāng)做函數(shù)使用,也可以當(dāng)做對(duì)象使用
第一種情況,super作為函數(shù)調(diào)用時(shí),代表父類的構(gòu)造函數(shù)。調(diào)用super的作用是形成子類的this對(duì)象,把父類的實(shí)例屬性和方法都放到這個(gè)this對(duì)象上面。
class A { constructor() { console.log(new.target.name); } } class B extends A { constructor() { super(); } } new A(); // A new B(); // B
第二種情況,super作為對(duì)象時(shí),在普通方法中,指向父類的原型對(duì)象;在靜態(tài)方法中,指向父類。
class A { p() { return 2; } } class B extends A { constructor() { super(); console.log(super.p()); // 2 } } let b = new B();
上面代碼中,子類B中的super.p(),將super當(dāng)做一個(gè)對(duì)象使用,這時(shí)super在普通對(duì)象中,指向的是A.prototype,super.p()相當(dāng)于A.prototype.p()。
由于super指向父類的原型對(duì)象,所以定義在父類實(shí)例上的方法或?qū)傩?,是無法通過super調(diào)用的。如下所示:
class A { constructor() { this.p = 2; } } class B extends A { get m() { return spuer.p; } } let b = new B(); b.m // undefined
為了解決這種問題,可以將屬性定義在父類的原型對(duì)象上
class A {}; A.prototype.x = 2; class B extends A { constructor() { super(); console.log(super.x); } } let b = new B();
ES6規(guī)定,在子類普通方法中通過super調(diào)用父類的方法時(shí),方法內(nèi)部的this指向當(dāng)前的子類實(shí)例
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()調(diào)用的是A.prototype.print(),但是此時(shí)方法內(nèi)部的this指向是子類B的實(shí)例,所以輸出2。
由于this指向的是子類實(shí)例,所有如果通過super對(duì)某個(gè)屬性賦值,這時(shí)super就是this,賦值的屬性會(huì)變成子類實(shí)例的屬性
class A { constructor() { this.x = 1; } } class B extends A { constructor() { super(); this.x = 2; super.x = 3; console.log(super.x); //undefind console.log(this.x); // 3 } }
上面代碼中,super.x
賦值為3
,這時(shí)等同于對(duì)this.x
賦值為3
。而當(dāng)讀取super.x
的時(shí)候,讀的是A.prototype.x
,所以返回undefined
。
如果super作為對(duì)象,用在靜態(tài)方法之中,這時(shí)super將指向父類,而不是父類的原型對(duì)象。
class Parent { static myMethod(msg) { console.log('static', msg); } myMethod(msg) { console.log('instance', msg); } } class Children extends Parent { static myMethod(msg) { super.myMthod(msg); } myMethod(msg) { super.myMethod(msg); } } Child.myMethod(1); // static 1 var child = new Child(); child.myMethod(2); // instance 2
上面代碼中,super
在靜態(tài)方法之中指向父類,在普通方法之中指向父類的原型對(duì)象。
另外,在子類的靜態(tài)方法中通過super調(diào)用父類的方法時(shí),方法內(nèi)部的this指向當(dāng)前的子類,而不是子類的實(shí)例
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
在靜態(tài)方法m中,super.print指向父類的靜態(tài)方法,到那時(shí)this指向的是類B,而不是B的實(shí)例。
【推薦學(xué)習(xí):javascript高級(jí)教程】