1肯适、簡介
Class可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承变秦,這比ES5的通過修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多框舔。
class Point {
}
class ColorPoint extends Point {
}
上面代碼定義了一個ColorPoint類蹦玫,該類通過extends關(guān)鍵字,繼承了Point類的所有屬性和方法刘绣。但是由于沒有部署任何代碼樱溉,所以這兩個類完全一樣,等于復(fù)制了一個Point類纬凤。下面福贞,我們在ColorPoint內(nèi)部加上代碼。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x,y) // 調(diào)用父類的constructor(x,y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString() //調(diào)用父類的toString()
}
}
上面代碼中停士,constructor方法和toString方法之中挖帘,都出現(xiàn)了super關(guān)鍵字,它在這里表示父類的構(gòu)造函數(shù)恋技,用來新建父類的this對象拇舀。
子類必須在constructor方法中調(diào)用super方法,否則新建實(shí)例時(shí)會報(bào)錯蜻底。這是因?yàn)樽宇愖约旱膖his對象骄崩,必須先通過父類的構(gòu)造函數(shù)完成塑造,得到與父類同樣的實(shí)例屬性和方法,然后再對其進(jìn)行加工要拂,加上子類自己的實(shí)例屬性和方法抠璃。如果不調(diào)用super方法,子類就得不到this對象脱惰。
class Point { /*......*/ }
class ColorPoint extends Point {
constructor() {
}
}
let cp = new ColorPoint()
上面代碼中鸡典,ColorPoint繼承了父類Point,但是它的構(gòu)造函數(shù)沒有調(diào)用super方法枪芒,導(dǎo)致新建實(shí)例時(shí)報(bào)錯。
ES5的繼承谁尸,實(shí)質(zhì)是先創(chuàng)造子類的實(shí)例對象this舅踪,然后再將父類的方法添加到this上面(Parent.apply(this))。ES6的繼承機(jī)制完全不同良蛮,實(shí)質(zhì)是先將父類實(shí)例對象的屬性和方法抽碌,加到this上面(所以必須調(diào)用super方法),然后再用子類的構(gòu)造函數(shù)修改this决瞳。
如果子類沒有定義constructor方法货徙,這個方法會被默認(rèn)添加,代碼如下皮胡。也就是說痴颊,不管有沒有顯示定義,任何一個子類都有constructor方法屡贺。
class ColorPoint extends Point {
}
//等同于
class ColorPoint extends Point {
constructor(...args) {
super(...args)
}
}
另一個需要注意的地方是蠢棱,在子類的構(gòu)造函數(shù)中,只有調(diào)用super之后甩栈,才可以使用this關(guān)鍵字泻仙,否則會報(bào)錯。這是因?yàn)樽宇悓?shí)例的構(gòu)造量没,基于父類實(shí)例玉转,只有super方法才能調(diào)用父類實(shí)例。
2殴蹄、Object.getPrototypeOf()
Object.getPrototypeOf方法可以用來從子類上獲取父類究抓。
Object.getPrototypeOf(ColorPoint) === Point
// true
3、super關(guān)鍵字
super這個關(guān)鍵字袭灯,既可以當(dāng)作函數(shù)使用漩蟆,也可以當(dāng)作對象使用。在這種情況下妓蛮,它的用法完全不同怠李。
第一種情況,super作為函數(shù)調(diào)用時(shí),代表父類的構(gòu)造函數(shù)捺癞。ES6要求夷蚊,子類的構(gòu)造函數(shù)必須執(zhí)行一次super函數(shù)。
class A { }
class B extends A {
constructor() {
super()
}
}
注意髓介,super雖然代表了父類A的構(gòu)造函數(shù)惕鼓,但是返回的是子類B的實(shí)例,即super內(nèi)部this指的是B的實(shí)例唐础,因此super()在這里相當(dāng)于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指向當(dāng)前正在執(zhí)行的函數(shù)一膨⊙叫希可以看到,在super()執(zhí)行時(shí)豹绪,它指向的是子類B的構(gòu)造函數(shù)价淌,而不是父類A的構(gòu)造函數(shù),也就是說瞒津,super()內(nèi)部的this指向的是B蝉衣。
作為函數(shù)時(shí),super()只能用在子類的構(gòu)造函數(shù)之中巷蚪,用在其他地方就會報(bào)錯病毡。
class A { }
class B extends A {
m() {
super() // 報(bào)錯
}
}
第二種情況,super作為對象時(shí)屁柏,在普通方法中剪验,指向父類的原型對象;靜態(tài)方法中前联,指向父類功戚。
class A {
p () {
return 2
}
}
class B extends A {
constructor() {
super()
console.log(super.p()) // 2
}
}
let b = new B();
上面代碼中,子類B當(dāng)中的super.p()似嗤,就是將super當(dāng)作一個對象使用啸臀。這時(shí),super在普通方法之中烁落,指向A.prototype乘粒,所以super.p()就相當(dāng)于A.prototype.p()。
注意伤塌,由于super指向父類的原型對象灯萍,所以定義在父類實(shí)例上的方法或?qū)傩裕菬o法通過super調(diào)用的每聪。
class A {
constructor() {
this.p = 2
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B()
b.m // undefined
上面代碼中旦棉,p是父類A實(shí)例的屬性齿风,super.p就引用不到它。
如果屬性定義在父類的原型對象上绑洛,super就可以取到救斑。
class A {}
A.prototype.x = 2
class B extends A {
constructor() {
super()
console.log(super.x) // 2
}
}
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()绑蔫,但是A.prototype.print()內(nèi)部的this指向子類B的實(shí)例运沦,導(dǎo)致輸出的是2,而不是1配深。也就是說携添,實(shí)際上執(zhí)行的是super.print.call(this)。
由于this指向子類實(shí)例凉馆,所以如果通過super對某個屬性賦值,這時(shí)super就是this亡资,賦值的屬性會變成子類實(shí)例的屬性澜共。
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锥腻,這時(shí)等同于對this.x賦值為3嗦董。而當(dāng)讀取super.x的時(shí)候,讀的是A.prototype.x瘦黑,所以返回undefined京革。
如果super作為對象,用在靜態(tài)方法之中幸斥,這時(shí)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
另外甲葬,在子類的靜態(tài)方法中通過super調(diào)用父類的方法時(shí)廊勃,方法內(nèi)部的this指向當(dāng)前的子類,而不是子類的實(shí)例经窖。
注意坡垫,使用super的時(shí)候,必須顯示指定是作為函數(shù)画侣,還是作為對象使用冰悠,否則會報(bào)錯。
class A { }
class B extends A {
constructor() {
super()
console.log(super) //報(bào)錯
}
}
上面代碼中配乱,console.log(super)當(dāng)中的super溉卓,無法看出是作為函數(shù)使用皮迟,還是作為對象使用,所以JavaScript引擎解析代碼的時(shí)候就會報(bào)錯的诵。這時(shí)万栅,如果能清晰地表明super的數(shù)據(jù)類型,就不會報(bào)錯西疤。
4烦粒、類的prototype屬性和proto屬性
Class作為構(gòu)造函數(shù)的語法糖,同時(shí)有prototype屬性和proto屬性代赁,因此同時(shí)存在兩條繼承鏈扰她。
(1)子類的proto屬性,表示構(gòu)造函數(shù)的繼承芭碍,總是指向父類徒役。
(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_屬性指向父類A的prototype屬性鸳吸。
class A { }
class B { }
// B的實(shí)例繼承A的實(shí)例
Object.setPrototypeOf(B.prototype, A.prototype)
// B 繼承 A的靜態(tài)屬性
Object.setPrototypeOf(B, A)
const b = new B()
Object.setPrototypeOf(B.prototype, A.prototype)
// 等同于
B.prototype._proto_ = A.prototype
Object.setPrototypeOf(B, A)
// 等同于
B._protot_ = A;
這兩條繼承鏈,可以這樣理解:作為一個對象速勇,子類(B)的原型(proto屬性)是父類(A)晌砾;作為一個構(gòu)造函數(shù),子類(B)的原型對象(prototype屬性)是父類的原型對象(prototype屬性)的實(shí)例烦磁。
B.prototype = Object.create(A.prototype)
//等同于
B.prototype._proto_ = A.prototype