Class 的继承

it2022-05-08  1

前面的话

es5之前的继承大多以原型链继承为主,但原型链继承的缺点很大。es6引入的类的继承方便的了不少。

es5模拟继承

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);// true

Square 继承了Rectangle,为了这样做,必须用一个Object.create()创建一个原型为Rectangle.prototype 的对象来重写Square.prototype,并且要调用Rectangle.call()方法,改变this的指向。

super关键字

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);// true

Square类通过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 2

super在静态方法中指向父类。

在执行Child.myMethod(1)时,执行的是子类的静态方法,在静态方法中,super指向的是父类super.myMethod(1),相当于parent.myMethod(1),即执行父类的静态方法,所以输出 ‘static 1’。

在执行child.myMethod(2)时,访问的是子类原型上的方法myMethod,这个方法是一个普通函数,super指向父类的原型,相当于执行父类原型上的myMethod方法,所以输出‘instance 2’。

类的Prototype属性与__proto__属性

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

最新回复(0)