深入理解ES6-9.类(2)

26 阅读3分钟

继承和派生类

继承

在类中,我们只需要使用 extends 关键字指定继承的函数,通过 super() 方法范围基类的构造函数。

class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
    getArea() {
        return this.length * this.width;
    }
}
class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }
}

派生类

继承自其他类的类叫做 派生类,如果在派生类中不指定构造函数,则在创建类实例的时候会自动调用 super() 并传入所有参数,但是最好主动调用 super() 方法。

派生类也可以继承父类的静态方法,这使用 es5 中的继承是做不到的。

super()

使用 super() 的注意点:

  1. super() 只能使用在派生类的构造函数中,否则会报错。
  2. 在构造函数中访问 this 时,一定要先调用 super(),否则会报错。
  3. 如果在派生类中指定了构造函数,就必须调用 super(),否则会报错。如果不想调用 super() 又不想报错,唯一方法是让构造函数返回一个 对象

extends

只要表达式可以被解析成一个函数(最后的结果是一个函数),且具有 [[construct]] 属性和原型(箭头函数、生成器不可以),就可以使用 extends 进行派生。

mixin 方法实现多继承

function A() {...}
function B() {...}
function mixin(...minxins) {
  var base = function() {}
  Object.assign(base.prototype, ...minxins)
  return base
}
class SubClass extends mixin(A, B) {{...}}

内建对象的继承

ES6之前,无法通过继承的方式创建属于自己的特殊数组的,也就是说可以使用原型链来继承数组的一些方法,但是还是有一些不同的:

  1. 子类生成的实例不是数组[],而是一个对象{}
  2. 某些返回一个数组的方法(例如slice等),返回的值还是一个Array的实例,而不是子类的实例。
  3. 没能从数组中继承其内部属性。有一个内部属性用于控制数值型属性和length属性同时更新,所以会导致通过下标添加元素时,length值不变
function MyArray() {
    Array.apply(this, arguments);
}
MyArray.prototype = Object.create(Array.prototype, {
    constructor: {
        value: MyArray,
        writable: true,
        configurable: true,
        enumerable: true
    }
});
// 1
var arr = new Array();     // []
var colors = new MyArray();  // MyArray {}
// 2
colors.slice(0,1)   // []
// 3
colors[0] = "red";    // MyArray {0: 'red'},此时colors.length仍为0
colors.push('blue');   // MyArray ['blue'],此时colors.length为1

但是使用 classextends 关键字实现继承,是可以避免这些不同的。

继承内部属性

可以将内部属性一起继承的。

class MyArray extends Array {}
var colors = new MyArray();   // MyArray []

Symbol.species

原本在内建对象中返回实例自身的方法,将自动返回派生类的实例。

class MyArray extends Array {}
var colors = new MyArray();   // MyArray []
colors.slice(0,1)   // MyArray []

在浏览器引擎背后是通过 Symbol.species 来实现的。Symbol.species属性是一个静态的访问器属性,只有get没有set,会返回一个构造器

很多内建对象中都定义了 Symbol.species属性,其中的默认实现都是返回 this。

class MyClass {
    static get[Symbol.species]() {
        return this;
    }
    constructor(value) {
        this.value = value;
    }
}

派生类也会继承静态属性,所以派生类中也会有 Symbol.species属性,同样返回 this,这时的 this 指向的就是派生类了。当然我们也可以强制在 派生类中覆写 Symbol.species属性,返回父类的构造器。

class MyClass {
    static get[Symbol.species]() {
        return this;
    }
    constructor(value) {
        this.value = value;
    }
    clone() {
        return new this.constructor[Symbol.species](this.value);
    }
}
class SubClassl extends MyClass {
    say(){...}
}
class SubClass2 extends MyClass {
    static get[Symbol.species]() {
        return MyClass;
    }
    say(){... }
}
let instancel = new MyDerivedClassl("foo"),
    clonel = instancel.clone(),              // SubClass1 {value: "foo"}
    instance2 = new MyDerivedClass2("bar"),
    clone2 = instance2.clone();              // MyClass {value: "bar"}

new.target

在类的构造函数中,也可以通过 new.target 确定类是如何被调用的,通常 new.target 等于类的构造函数。但是在 new 一个子类的时候,new.target 是子类的构造函数。