Class的继承
简介
Class可以通过extends关键字实现继承,这比es5通过修改原型链实现继承,要清晰和方便许多
class Point {
}
class ColorPoint extends Point {
} //继承了Point类的所有属性和方法
//由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
//两个方法中都出现了super关键字,在这里表示父类的构造函数,用来新建父类的this对象
子类必须在constructor方法中调用super方法,否则新建实例的时候会报错
这是因为子类自己的this对象,必须通过父类的构造函数完成塑造,得到于父类同样的实例属性和方法,然后对其进行加工,加上子类自己的实例属性和方法
如果不调用super方法,子类就不能得到this对象
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
}
}
let cp = new ColorPoint(); // ReferenceError
//子类没有调用super方法,导致新建实例时报错
ES5和ES6继承方式的不同
ES5:先创造子类的实例对象this,然后再将父类的方法添加到this上面(
Parent.apply(this)
)ES6:先将父亲实例对象的属性和方法,添加到this上面(所以必须先调用super方法),然后再用子类构造函数修改this
如果子类没有定义constructor方法,这个方法会被默认添加,不管有没有显示定义,任何一个子类都有constructor方法
class ColorPoint extends Point {
}
// 等同于
class ColorPoint extends Point {
constructor(...args) {
super(...args);
}
}
注意:只有调用了super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例
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; // 正确
}
}
父类的静态方法,也会被子类继承
class A {
static hello() {
console.log('hello world');
}
}
class B extends A {
}
B.hello() // hello world
Object.getPrototypeOf( )
Object.getPrototypeOf
方法可以用来从子类上获取父类
判断一个类是否继承了另一个类
Object.getPrototypeOf(ColorPoint) === Point
// true
super关键字
super这个关键字,可以当作函数使用,也可以当作对象使用
第一种情况:函数
super当作函数调用时,代表父类的构造函数
ES6规定,子类的构造函数必须执行一次super函数
class A {}
class B extends A {
constructor() {
super(); //代表父类的构造函数
}
}
注意:super虽然代表了父类A的构造函数,但是返回的时子类B的实例,即super内部this指的是B的实例,因此super()
在这里相当于A.prototype.constructor.call(this)
class A { constructor() { console.log(new.target.name); }}class B extends A { constructor() { super(); }}new A() // Anew B() // B//在super执行时,它指向的是子类B的构造函数,而不是父类A的构造函数//也就是说,super内部的this指向的是B
作为函数时,super只能用在子类的构造函数之中,用在其他地方就会报错
class A {}class B extends A { m() { super(); // 报错,用在了B子类的m方法中 }}
第二种情况:对象
super在作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类
class A { p() { return 2; }}class B extends A { constructor() { super(); console.log(super.p()); // 2 //此处的super.p()就是将super当成一个对象使用,在普通方法中,就指向A.prototype,所以super.p()就相当于A.prototype.p() }}let b = new B();
注意:由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的
class A { constructor() { this.p = 2; //此处p的父类A实例的属性 }}class B extends A { get m() { return super.p; //引用不到A中实例的属性 }}let b = new B();b.m // undefined
如果属性定义在父类的原型对象上,super就可以取到
class A {}A.prototype.x = 2; //x是定义在A.prototype上面的class B extends A { constructor() { super(); console.log(super.x) // 2 }}let b = new B();
在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例
class A { constructor() { this.x = 1; } print() { console.log(this.x); }}class B extends A { constructor() { super(); this.x = 2; } m() { super.print(); //调用了父类的方法,也就是A.prototype.print(),但是A.prototype.print()内部的this指向子类B的实例 }}let b = new B();b.m() // 2
由于this
指向子类实例,所以如果通过super
对某个属性赋值,这时super
就是this
,赋值的属性会变成子类实例的属性
如果super作为对象,用在静态方法中,这是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
在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例
注意:在使用super时,必须显式指定时作为函数还是对象,否则会报错
类的prototype属性和__proto__
属性
大多数浏览器的ES5实现之中,每一个对象都有__proto__
属性,之相对应的构造函数的prototype属性
Class 作为构造函数的语法糖,同时有prototype
属性和__proto__
属性,因此同时存在两条继承链
- 子类的
__proto__
属性,表示构造函数的继承,总是指向父类- 子类
prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性
class A {}class B extends A {}B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true
这两条继承链可以这样理解:作为一个对象,子类(B)的原型(__proto__
属性)是父类(A),作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例
B.prototype = Object.create(A.prototype);// 等同于B.prototype.__proto__ = A.prototype;
原型链,prototype和__proto__
的区别
JavaScript中所有的对象都是Object的实例,并继承了Object.prototype的属性和方法,也就是说,Object.prototype是所有对象的爸爸
在对象创建时,就会有一些预定义的属性,其中定义函数的时候,这个预定义属性就是prototype,这个prototype是一个普通的对象
而定义普通的对象的时候,就会生成一个__proto__
,这个__proto__
指向的是这个对象的构造函数的prototype
原型链:对象都有一个toString方法,实例化对象b也可以toString
而实例化对象b本身并没有toString的方法,那他就会沿着它的__proto__
向他的构造函数B的prototype对象去找,而这里也没有,那他就会 继续沿着B.prototype.__proto__
向上找。而B.prototype.__proto__
指向的就是Object.prototype
extends关键字后面可以跟多种类型的值,只要是一个prototype属性的函数,就能被继承,由于函数都有prototype属性(除了Function.prototype
函数),所以可以是任意函数
两种情况:
子类继承Object类
class A extends Object {}A.__proto__ === Object // trueA.prototype.__proto__ === Object.prototype // true
不存在任何继承
class A {}A.__proto__ === Function.prototype // trueA.prototype.__proto__ === Object.prototype // true
实例的__proto__
属性
子类实例的__proto__
属性的__proto__
属性,指向父类实例的__proto__
属性
也就是说,子类的原型的原型,是父类的原型
var p1 = new Point(2, 3);var p2 = new ColorPoint(2, 3, 'red');p2.__proto__ === p1.__proto__ // falsep2.__proto__.__proto__ === p1.__proto__ // true
通过子类实例的__proto__.__proto__
属性,可以修改父类实例的行为
p2.__proto__.__proto__.printName = function () { console.log('Ha');};p1.printName() // "Ha"
原生构造函数的继承
ES6允许继承原生构造定义子类,因为ES6是新建父类实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承
class MyArray extends Array { constructor(...args) { super(...args); }}var arr = new MyArray();arr[0] = 12;arr.length // 1arr.length = 0;arr[0] // undefined