es5之前的继承大多以原型链继承为主,但原型链继承的缺点很大。es6引入的类的继承方便的了不少。
es5 实现继承需要多个步骤:
function Rectangle(length, width) { this.length = length; this.width = width; } Rectangle.prototype.getArea = function () { return this.length * this.width; } function Square(length) { Rectangle.call(this, length, length); } Square.prototype = Object.create(Rectangle.prototype, { constructor: { value: Square, enumerable: true, writable: true, configurable: true } }); var square = new Square(3); console.log(square.getArea());// 9 console.log(square instanceof Square);// true console.log(square instanceof Rectangle);// trueSquare 继承了Rectangle,为了这样做,必须用一个Object.create()创建一个原型为Rectangle.prototype 的对象来重写Square.prototype,并且要调用Rectangle.call()方法,改变this的指向。
es6中的类让我们很轻松的实现继承的功能,使用熟悉的extends关键字可以指定类继承的函数。原型会自动调整,通过调用super()方法即可访问基类的构造函数。
super关键字既可以当作函数使用,也可以当作对象使用。这两种情况下,它的用法完全不同。
[作为函数调用]
作为函数调用时代表父类的构造函数。
class Rectangle { constructor (length, width) { this.length = length; this.width = width; } getArea() { return this.length * this.width; } } class Square extends Rectangle { constructor (length) { // 与Rectangle.call(this, length, length)相同 super(length, length); } } var square = new Square(3); console.log(square instanceof Square);// true console.log(square instanceof Rectangle);// trueSquare类通过extends关键字继承Rectangle类,在Square构造函数中通过super()调用Rectangle构造函数,并传入相应的参数。
这里子类Square的构造函数中的super()代表调用父类的构造函数,但super内部的this指向的是Square。 因此super()在这里相当于 Square.prototype.constructor.call(this).
[注意]
继承自其他类的派生类(子类)在constructor方法中必须调用super()方法,否则新建实例会报错。
在子类的构造函数中,只有调用了super之后才可以使用this。
class Rectangle { constructor (length, width) { this.length = length; this.width = width; } getArea () { return this.length * this.width; } } class Square extends Rectangle { constructor(length){ // this.length = length; // 报错 super(length,length); this.length = length; } } let square = new Square(5); console.log(square.length);// 5 console.log(square.getArea());// 25原因是因为子类没有自己的this对象,而是继承的父类的this对象,然后对其加工。如果不调用super()方法, 子类就得不到this对象。
如果子类没有定义constructor方法,那么这个方法会被默认添加
class Square extends Rectangle { // 没有构造器 } // 等价于 class Square extends Rectangle { constructor(...args) { super(...args) } }[作为对象]
在普通方法中指向父类的原型对象在静态方法中指向父类在普通方法中:
class A { constructor() { this.x =1; } p() { return 2; } print() { console.log(this.x); } } A.prototype.y = 3; class B extends A { constructor() { super(); this.x = 4; console.log(super.p()) ; } get m() { return super.x; } get n() { return super.y; } G() { super.print(); } } let b = new B(); // 2 console.log(b.m);// undefined console.log(b.n);// 3 b.G();// 4在创建b实例时,constructor方法内执行super.p(),这里的super为对象,指向A.prototype,相当于A.prototype.p(),所以会输出2。
在调用b.m时,执行super.x,相当于A.prototype.x,而A.prototype没有x,只有实例的x,所有定义在父类实例上的方法与属性是无法通过super调用的。
在调用b.n时,此时A.prototype.y = 3,所以输出3。
在调用b.G()时,返回的是B类实例上的x,说明super调用父类方法时,还是会绑定子类的this。所以输出4。
在静态方法中:
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 let child = new Child(); child.myMethod(2);// instance 2super在静态方法中指向父类。
在执行Child.myMethod(1)时,执行的是子类的静态方法,在静态方法中,super指向的是父类super.myMethod(1),相当于parent.myMethod(1),即执行父类的静态方法,所以输出 ‘static 1’。
在执行child.myMethod(2)时,访问的是子类原型上的方法myMethod,这个方法是一个普通函数,super指向父类的原型,相当于执行父类原型上的myMethod方法,所以输出‘instance 2’。
Class作为构造函数的语法糖,同时拥有prototype属性和__proto__属性,因此同时存在两条继承链
子类的__proto__属性表示构造函数的继承,总是执行父类子类的prototype属性的__proto__属性表示方法的继承,总是指向父类的prototype属性 class Parent { constructor(name) { this.name = name; } } class Child extends Parent{ constructor(name, age) { super(name); this.age = age; } getName() { console.log(this.name); } } let parent = new Parent('xiao') let child = new Child('xiaoqi', 20); console.log(Child.prototype.__proto__ === Parent.prototype);// true console.log(Child.__proto__ === Parent );// true console.log(child.__proto__ === Child.prototype);// true console.log(child.__proto__.__proto__ === parent.__proto__ );// true这两条继承链可以这样理解:
作为对象,子类的原型(__proto__属性)是父类
作为构造函数,子类的原型(prototype属性)是父类的实例
Object.create(Parent.prototype); // 等同于 Child.prototype.__proto__ = Parent.prototype;另外,子类实例的__proto__的__proto__属性指向父类实例的__proto__属性,即子类的原型的原型是父类的原型
使用es5定义一个继承Array的函数:
function MyArray() { Array.apply(this, arguments); } MyArray.prototype = Object.create(Array.prototype, { constructor: { value: MyArray, writable: true, configurable: true, enumerable: true } }) var colors = new MyArray(); colors[0] = 'red'; console.log(colors.length);// 0 colors.length = 0; console.log(colors[0]);// 'red'之所以发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过Array.apply()或者分配给原型对象都不行。原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性。
在es5中 ,先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。
es6允许继承原生构造函数,因为es6先新建父类的实例对象this,然后在用子类的构造函数修饰this,使得父类的所有行为都得到继承。
class MyArray extends Array{ constructor(...args){ super(...args); } } var arr = new MyArray(); arr[0] = 12; console.log(arr.length);// 1 arr.length = 0; console.log(arr[0]);// undefined }Object.getPrototypeOf():
使用该方法可以用来从子类上获取父类,即可以判断一个类是否继承了另一个类。
class Parent { } class Child extends Parent { } console.log(Object.getPrototypeOf(Child) === Parent);// true