一、簡單使用
- Class通過extends關鍵字實現繼承,其實質是先創造出父類的this對象,然后用子類的構造函數修改this
- 子類的構造方法中必須調用super方法,且只有在調用了super()之后才能使用this,因為子類的this對象是繼承父類的this對象,然后對其進行加工,而super方法表示的是父類的構造函數,用來新建父類的this對象
class Animal {
constructor(kind) {
this.kind = kind
}
getKind() {
return this.kind
}
}
// 繼承Animal
class Cat extends Animal {
constructor(name) {
// 子類的構造方法中必須先調用super方法
super('cat');
this.name = name;
}
getCatInfo() {
console.log(this.name + ':' + super.getKind())
}
}
const cat1 = new Cat('xiaohei');
cat1.getCatInfo(); // xiaohei:cat
二、super關鍵字
從上面的例子中發現,super關鍵字不僅可以作為函數使用,還可以作為對象使用。在這兩種情況下,super的用處也是不一樣的。
2.1 super函數
super作為函數使用時,表示父類的構造函數。
不過要注意的是,super雖然代表父類的構造函數,但是返回的是子類的實例。這里的super相當於父類.prototype.constructor.call(子類this);
class Animal {
constructor(kind) {
this.kind = kind
console.log(this)
}
}
class Cat extends Animal {
constructor(name) {
super('cat');
this.name = name;
}
}
// 調用super()函數,相當於:
// Animal.prototype.constrctor.call(this)
// 最后再返回this
const cat1 = new Cat('xiaohei'); // Cat { kind: 'cat' }
注意:super作為函數時,只能在子類的構造函數中調用,在其他地方調用會報錯。
2.2 super對象
- 在普通方法中指向父類的原型對象
class Animal {
constructor(kind) {
this.kind = kind
console.log(this)
}
getKind() {
return this.kind
}
}
Animal.prototype.color = 'black'
class Cat extends Animal {
constructor(name) {
super('cat');
this.name = name;
}
getCatInfo() {
// super在普通方法中表示的是Animal.prototype:
// super.color相當於Animal.prototype.color
console.log(super.color); // black
// super.getKind()相當於Animal.prototype.getKind()
console.log(this.name + ':' + super.getKind())
}
}
const cat1 = new Cat('xiaohei'); // Cat { kind: 'cat' }
cat1.getCatInfo(); // xiaohei:cat
注意:
1)由於super表示的是父類的原型,因此在父類實例上的屬性和方法都無法通過super調用
getCatInfo() {
// kind是Animal的實例屬性,因此無法通過super訪問
console.log(super.kind); // undefined
console.log(this.name + ':' + super.getKind()); // xiaohei:cat
}
2)ES6規定,通過super調用父類的方法時,super會綁定子類的this
class Foo {
constructor() {
this.num = 1;
}
print() {
console.log(this.num);
}
}
class Bar extends Foo {
constructor() {
super();
this.num = 2;
}
write() {
// super.print()相當於Foo.prototype.print.call(this)
super.print();
}
}
const bar = new Bar();
bar.write(); // 2
3)通過super對某個屬性賦值,相當於在子類上添加了一個實例屬性,因此super會綁定子類的this
class Foo {
constructor() {
this.num = 1;
}
}
class Bar extends Foo {
constructor() {
super();
this.num = 2;
console.log(this.num); // 2
// 相當於this.num = 3
super.num = 3;
console.log(this.num); // 3
// 通過super.num讀取值,相當於Foo.prototype.num
console.log(super.num); // undefined
}
}
const bar = new Bar();
- 在靜態方法中指向父類
class Foo {
static print() {
console.log('static method');
}
write() {
console.log('normal method');
}
}
class Bar extends Foo {
static print() {
super.print()
}
write() {
super.write()
}
}
const bar = new Bar();
Bar.print(); // static method
bar.write(); // normal method
三、類的prototype屬性和_proto_屬性
在大多數瀏覽器的ES5實現中,每一個對象都有一個_proto_屬性,指向其構造函數的prototype屬性。Class作為構造函數的語法糖,同時有prototype屬性和_proto_屬性,因此同時存在兩條繼承鏈:
- 子類的_proto_屬性表示構造函數的繼承,指向父類 => 在ES5中,對象的_proto_屬性指向其原型對象,在Class中的理解是一樣的,子類的_proto_屬性也是指向其原型對象,因此指向其父類。
- 子類的prototype屬性的_proto_屬性表示方法的繼承,指向父類的prototype屬性 => 在ES5中,只有函數對象才有prototype屬性,ES6中的Class實際上可以看作是一個語法糖,Class的數據類型是函數,也具有prototype屬性,在子類繼承父類的時候,子類的prototype也繼承了父類的prototype屬性,因此子類的prototype屬性的_proto_屬性指向父類的prototype。
用代碼表示上面的描述就是:
class Foo {}
class Bar {}
Object.setPrototypeOf(Bar, Foo)
Object.setPrototypeOf(Bar.prototype, Foo.prototype)
// 子類Bar的_proto_屬性指向父類Foo
console.log(Bar._proto_ === Foo) // true
console.log(Object.getPrototypeOf(Bar) === Foo) // true
// 子類Bar的prototype屬性的_proto_屬性指向父類Foo的prototype屬性
console.log(Bar.prototype._proto_ === Foo.prototype) // true
console.log(Object.getPrototypeOf(Bar.prototype) === Foo.prototype) // true
class Foo {}
class Bar extends Foo {}
// 子類Bar的_proto_屬性指向父類Foo
console.log(Bar._proto_ === Foo) // true
console.log(Object.getPrototypeOf(Bar) === Foo) // true
// 子類Bar的prototype屬性的_proto_屬性指向父類Foo的prototype屬性
console.log(Bar.prototype._proto_ === Foo.prototype) // true
console.log(Object.getPrototypeOf(Bar.prototype) === Foo.prototype) // true
- 子類實例的_proto_屬性的_proto_屬性指向父類實例的_proto_屬性,也就是說子類的原型的原型是父類的原型。
class Foo {}
class Bar extends Foo {}
const foo = new Foo()
const bar = new Bar()
console.log(Object.getPrototypeOf(Object.getPrototypeOf(bar)) === Object.getPrototypeOf(foo)) // true
console.log(bar._proto_._proto_ === foo._proto_) // true
因此可以通過子類的原型的原型來修改父類的原型,蜜汁操作,不建議
// bar._proto_.proto_.print = function() {
// console.log('haha')
// }
Object.getPrototypeOf(Object.getPrototypeOf(bar)).print = function() {
console.log('haha')
}
bar.print() // haha
foo.print() // haha
四、extends的繼承目標
目標:含有prototype屬性的對象,因此可以是任意函數(除了Function.prototype,不要忘記了Function.prototype也是函數,typeof Function.prototype === 'function')。
- 通過Class繼承原生構造函數
ECMAScript中的原生構造函數大致有以下這些:- Boolean()
- Number()
- String()
- Object()
- Array()
- Function()
- Date()
- RegExp()
- Error()
