一、简单使用
- 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()