Как Babel из серии ES6 компилирует класс (ниже)

внешний интерфейс GitHub JavaScript Babel
Как Babel из серии ES6 компилирует класс (ниже)

предисловие

в предыдущем посте«Как Babel серии ES6 компилирует класс (часть 1)», мы знаем, как Babel компилирует класс В этой статье мы узнаем, как Babel реализует наследование классов с помощью ES5.

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

Схематическая диаграмма цепи прототипа:

寄生组合式继承原型链示意图

О паразитическом композиционном наследовании мы имеем вНесколько способов, преимущества и недостатки глубокого наследования в JavaScriptвведено в.

Цитируя похвалу паразитическому композиционному наследованию в «Advanced JavaScript Programming»:

Эффективность этого подхода заключается в том, что он вызывает конструктор Parent только один раз и, таким образом, позволяет избежать создания ненужных избыточных свойств в Parent.prototype. При этом цепочка прототипов остается неизменной, поэтому можно нормально использовать instanceof и isPrototypeOf. Разработчики в целом согласны с тем, что паразитное композиционное наследование является идеальной парадигмой наследования для ссылочных типов.

ES6 extend

Класс, достигнутый за счет наследования, расширяет ключевое слово, это наследование путем изменения цепочки прототипов, чем 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 представляет конструктор родительского класса, эквивалентный Parent.call(this) в ES5.

Подклассы должны вызывать метод super в методе конструктора, иначе при создании нового экземпляра будет сообщено об ошибке. Это связано с тем, что подкласс не имеет собственного объекта this, а наследует объект this родительского класса и затем обрабатывает его. Если вы не вызовете метод super, подклассы не получат этот объект.

Именно по этой причине в конструкторе подкласса ключевое слово this можно использовать только после вызова super, иначе будет сообщено об ошибке.

__proto__ подкласса

В ES6 статический метод родительского класса может быть унаследован подклассом. Например:

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

class Bar extends Foo {
}

Bar.classMethod(); // 'hello'

Это связано с тем, что класс, как синтаксический сахар для конструкторов, имеет как свойство прототипа, так и свойство __proto__, поэтому одновременно существуют две цепочки наследования.

(1) Атрибут __proto__ подкласса, указывающий на наследование конструктора, всегда указывает на родительский класс.

(2) Свойство __proto__ свойства прототипа подкласса, указывающее на наследование метода, всегда указывает на свойство прототипа родительского класса.

class Parent {
}

class Child extends Parent {
}

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

Схематическая диаграмма цепочки прототипов ES6:

ES6 class 原型链示意图

Мы обнаружим, что по сравнению с паразитным наследованием композиции, ES6 имеет еще один класс.Object.setPrototypeOf(Child, Parent)Шаг.

цель наследования

За ключевым словом extends может следовать несколько типов значений.

class B extends A {
}

A в приведенном выше коде, если это функция со свойством прототипа, может быть унаследована B. Поскольку все функции имеют свойство прототипа (кроме функции Function.prototype), A может быть любой функцией.

В дополнение к функциям значение A также может быть нулевым, когдаextend nullкогда:

class A extends null {
}

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

Вавилонская подборка

Этот код 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 } });

Роль заключается в добавлении настраиваемого атрибута CONSTRUCTOR, который может быть записан в Subclass.Prototype, который является подклассом.

_possibleConstructorReturn

Функция вызывается так:

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) должно быть неопределенным. Но что, если мы вернемся в функции-конструкторе? Например:

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

Мы можем возвращать значения различных типов, даже null:

class Parent {
    constructor() {
        return null
    }
}

Давайте посмотрим на это суждение:

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

Обратите внимание, что смысл этого предложения не в том, чтобы судить о том, существует ли вызов.Если он существует, выполнить его.(typeof call === "object" || typeof call === "function") ? call : self

потому что&&приоритет оператора выше, чем? :, поэтому предложение должно означать:

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

Для значения Parent.Call (это), если это тип объекта или тип функции, он вернет Parent.Call (это), если оно нет или значение базового типа или undefined, он вернет себя , который является этим подклассом.

Это также почему эта функция названа_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 определяется в соответствии с типом возвращаемого значения конструктора Parent.

Наконец, согласно конструктору подкласса, измените значение _this и верните это значение.

серия ES6

Адрес каталога серии ES6:GitHub.com/ в настоящее время имеет бриз…

Ожидается, что в серии ES6 будет написано около 20 статей, направленных на углубление понимания некоторых точек знаний ES6, с акцентом на область действия на уровне блоков, шаблоны меток, функции стрелок, реализацию моделирования символов, наборов, карт и обещаний, схему загрузки модулей, асинхронность. обработка и т.п. содержание.

Если есть какие-либо ошибки или неточности, пожалуйста, поправьте меня, большое спасибо. Если вам нравится или у вас есть вдохновение, добро пожаловать в звезду, что также является поощрением для автора.