1. 90前端首页
  2. 前端开发
  3. JavaScript

ES6系列之Babel是如何编译Class的(下)

前言

在上一篇《 ES6系列Babel是如何编译Class的(上)》中,我们知道了Babel是如何编译Class的,介绍我们学习Babel是如何用ES5实现Class的继承。

ES5寄生组合式继承

function Parent (name) {
    this.name = name;
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}

Child.prototype = Object.create(Parent.prototype);

var child1 = new Child('kevin', '18');

console.log(child1);

原型链示意图为:

ES6系列之Babel是如何编译Class的(下)

关于寄生组合式继承我们在《 JavaScript深入之继承的多种方式和优缺点》中介绍过。

引用《 JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:

这种方式的高效率体现它只调用了一次Parent构造函数,并且因此避免了在Parent.prototype上面创建的,多余的属性。同时,原型链还能保持不变;因此,还能够正常使用instanceof和isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

ES6扩展

Class通过扩展关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

其他ES5的代码对应到ES6就是:

class Parent {
    constructor(name) {
        this.name = name;
    }
}

class Child extends Parent {
    constructor(name, age) {
        super(name); // 调用父类的 constructor(name)
        this.age = age;
    }
}

var child1 = new Child('kevin', '18');

console.log(child1);

指出的是:

super关键字表示父类的构造函数,相当于ES5的Parent.call(this)。

这是因为子类没有自己的这个对象,而是继承父类的这个对象,然后进行进行加工。如果不调用super方法,子类就得不到这个对象。

也正是因为这个原因,在子类的构造函数中,只有调用super之后,才可以使用此关键字,否则会报错。

子类的__proto__

在ES6中,父类的静态方法,可以被子类继承。举个例子:

class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
}

Bar.classMethod(); // 'hello'

这是因为类作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

class Parent {
}

class Child extends Parent {
}

console.log(Child.__proto__ === Parent); // true
console.log(Child.prototype.__proto__ === Parent.prototype); // true

ES6的原型链示意图为:

ES6系列之Babel是如何编译Class的(下)

我们会发现,参照寄生组合式继承,ES6的class多了一个Object.setPrototypeOf(Child, Parent)的步骤。

继承目标

扩展关键字后面可以跟多种类型的值。

class B extends A {
}

上面代码的A,只要是一个有原型属性的函数,就可以被B继承。由于函数都有prototype属性(除了Function.prototype函数),因此A可以是任意函数。

除了函数之外,A的值还可以是null,当extend null的时候:

class A extends null {
}

console.log(A.__proto__ === Function.prototype); // true
console.log(A.prototype.__proto__ === undefined); // true

Babel编译

那ES6的这段代码:

class Parent {
    constructor(name) {
        this.name = name;
    }
}

class Child extends Parent {
    constructor(name, age) {
        super(name); // 调用父类的 constructor(name)
        this.age = age;
    }
}

var child1 = new Child('kevin', '18');

console.log(child1);

Babel又是如何编译的呢??我们可以在Babel官网的Try it out尝试

'use strict';

function _possibleConstructorReturn(self, call) {
    if (!self) {
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    return call && (typeof call === "object" || typeof call === "function") ? call : self;
}

function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });
    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

var Parent = function Parent(name) {
    _classCallCheck(this, Parent);

    this.name = name;
};

var Child = function(_Parent) {
    _inherits(Child, _Parent);

    function Child(name, age) {
        _classCallCheck(this, Child);

        // 调用父类的 constructor(name)
        var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name));

        _this.age = age;
        return _this;
    }

    return Child;
}(Parent);

var child1 = new Child('kevin', '18');

console.log(child1);

我们可以看到Babel创建了_inherits函数帮助实现继承,又创建了_possibleConstructorReturn函数帮助确定调用父类构造函数的返回值,我们来细致的看一看代码。

_inherits

function _inherits(subClass, superClass) {
    // extend 的继承目标必须是函数或者是 null
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    }

    // 类似于 ES5 的寄生组合式继承,使用 Object.create,设置子类 prototype 属性的 __proto__ 属性指向父类的 prototype 属性
    subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });

    // 设置子类的 __proto__ 属性指向父类
    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

关于Object.create(),一般我们用的时候会设置一个参数,其实是支持初始化两个参数的,第二个参数表示要添加到新创建对象的属性,注意这里是给新创建的对象即返回值添加属性,而不是在新创建对象的原型对象上添加。

举个例子:

// 创建一个以另一个空对象为原型,且拥有一个属性 p 的对象
const o = Object.create({}, { p: { value: 42 } });
console.log(o); // {p: 42}
console.log(o.p); // 42

再完整一点:

const o = Object.create({}, {
    p: {
        value: 42,
        enumerable: false,
        // 该属性不可写
        writable: false,
        configurable: true
    }
});
o.p = 24;
console.log(o.p); // 42

那么对于这段代码:

subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });

作用就是给subClass.prototype添加一个可配置可写不可枚举的构造函数属性,该属性赋予subClass。

_possibleConstructor返回

函数里是这样调用的:

var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name));

我们简化为:

var _this = _possibleConstructorReturn(this, Parent.call(this, name));

_possibleConstructorReturn 的原始为:

function _possibleConstructorReturn(self, call) {
    if (!self) {
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    return call && (typeof call === "object" || typeof call === "function") ? call : self;
}

在这里我们判断Parent.call(this, name)的返回值的类型,咦?这个值还能有很多类型?

对于这样一个课程:

class Parent {
    constructor() {
        this.xxx = xxx;
    }
}

Parent.call(this,name)的值肯定是undefined。可是如果我们在构造函数函数中return了呢?此类:

class Parent {
    constructor() {
        return {
            name: 'kevin'
        }
    }
}

我们可以返回各种类型的值,甚至是null:

class Parent {
    constructor() {
        return null
    }
}

我们接着看这个判断:

call && (typeof call === "object" || typeof call === "function") ? call : self;

注意,这句话的意思不是判断call是否存在,如果存在,就执行 (typeof call === "object" || typeof call === "function") ? call : self

因为&&的运算符优先级高于? :,所以这句话的意思应该是:

(call && (typeof call === "object" || typeof call === "function")) ? call : self;

对于Parent.call(this)的值,​​如果是object类型或是函数类型,就返回Parent.call(this),如果是null或基本类型的值或者是undefined,都会返回self也就是子类的this。

这也是为什么这个函数被命名为_possibleConstructorReturn

总结

var Child = function(_Parent) {
    _inherits(Child, _Parent);

    function Child(name, age) {
        _classCallCheck(this, Child);

        // 调用父类的 constructor(name)
        var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name));

        _this.age = age;
        return _this;
    }

    return Child;
}(Parent);

最后我们总体看下如何实现继承:

首先执行_inherits(Child, Parent),建立孩子和父母的原型链关系,即Object.setPrototypeOf(Child.prototype, Parent.prototype)Object.setPrototypeOf(Child, Parent)

然后调用Parent.call(this, name),根据父级构造函数的返回值类型确定子类构造函数this的初始值_this。

最终,根据子类构造函数,修改_this的值,然后返回该值。

ES6系列

ES6系列目录地址:https : //github.com/mqyqingfeng/Blog

ES6系列预计写二十篇左右,预计加深ES6部分知识点的理解,重点讲解块级作用域,标签模板,箭头函数,符号,设置,映射以及Promise的模拟实现,模块加载方案,同步处理等内容。

如果有错误或者不严谨的地方,请考虑给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

本文来自网络整理,转载请注明原出处:https://segmentfault.com/a/1190000016942815

展开阅读全文

发表评论

登录后才能评论