Babel JS inheritance (2)

From the introduction of objects to several implementations of inheritance, it’s time to really look at how Babel handles class and extends.

How does Babel translate class

Suppose we have the following ES6 code:

// demo.js class SuperType { constructor(name) { this.superName = name; this.superFriends = ['Json']; } sayHi() { console.log("Super HI!" ); } static getVersion() {return "12.2"; }}Copy the code

Properties that contain primitive type values, properties that reference type values, instance methods, and a static method. Take a look at the translated Babel code to see how class is implemented. ! Attention! Static properties are not discussed here, although they are not behaviorally-specific, but require additional support. Let’s focus on the core.

// demo_babel.js "use strict"; function _classCallCheck(instance, Constructor) { if (! (instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var SuperType = /*#__PURE__*/ function () { function SuperType(name) { _classCallCheck(this, SuperType); this.superName = name; this.superFriends = ['Json']; } _createClass(SuperType, [{ key: "sayHi", value: function sayHi() { console.log("Super HI!"); } }], [{ key: "GetVersion ", value: function getVersion() {return "12.2"; return SuperType; } ();Copy the code

From top to bottom:

_classCallCheck: This is a common way to determine whether the call is normal or new. According to the execution principle of the new operator, this will be an inherent object to which the existing stereotype points, and it will be constructed by constructors to form instances of the corresponding type. Normal calls, on the other hand, are top-level objects without changing the execution context, so use instanceof to determine this.

_defineProperties: is a utility function that _createClass relies on, which is similar to object.defineProperty, as you can guess from the name and signature. To take a quick look at the logic, this is defineProperty that can be batched.

_createClass: The core method for creating a class, including adding attributes to the stereotype, which handles instance definitions, and attributes to the constructor itself, which handles static definitions.

Isolate the entire process by creating a large closure with a function that executes immediately. If we were to implement class ourselves using normal writing, it would look something like this:

Function SuperType(name) {// This. Name = name; this.superFriends = ['Json']; } supertype.getVersion = function () {return "12.1"}; SuperType.prototype.sayHi = function() { console.log("Super HI!" , this.name); };Copy the code

The class implementation of Babel, on the one hand, adds call checking, and on the other hand, the use of multiple instance methods in definition can be implemented in bulk via **_defineProperties**. So it doesn’t really look that different.

How does Babel translate class+extends

Seems to have finally gotten down to business. In order to reduce the amount of code posted, the following contains only incremental code:

// demo.js
class SubType extends SuperType {
  constructor(name, age) {
    super(name);
    this.age = age;
  }

  sayHi() {
    console.log("Sub HI!", this.name, this.age);
  }
}
Copy the code

After the translated code, the first part, first introduced the utility function:

// demo_babel.js function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj ! == Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass ! == "function" && superClass ! == null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } // The three utility functions described above // the declaration of SuperType listed aboveCopy the code

_typeof: is an extension of native typeof. In environments where Symbol is not available, this function can still determine the typeof the extension if Symbol is implemented by special means. Notice the ternary expression in the else branch.

_possibleConstructorReturn and _assertThisInitialized: the two together to see. This is explained in the invocation.

**_getPrototypeOf and _setPrototypeOf** : You’ll see in the MDN documentation that some browsers support __proto__ but not Object.getPrototypeof, so these two methods are used for compatibility.

_inherits

You can see that this method is different from other tool methods, and is H2! To isolate this function:

function _inherits(subClass, superClass) { if (typeof superClass ! == "function" && superClass ! == null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }Copy the code

If the superClass argument, the superClass argument, does not pass a constructor, it will throw an error, which makes sense.

The following code may be familiar to you. The last article introduced the centralized approach to inheritance, and finally the “parasitic combination” :

// This function only introduces the principle of parasitic combination, Function inheritPrototype(subType, constructor) superType) { let prototype = Object.create(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; }Copy the code

Note that Babel uses the Object. Create second argument more rigor in the constructor step. Enumerable is not configured, so it defaults to false and is not iterated. Specific can perform the Object. GetOwnPropertyDescriptor (Object. The prototype, “constructor”). Proto =superClass. Proto =superClass. Proto =superClass.

When you look at the code below, you will see that Babel is indeed an inheritance from the implementation of the parasitic combination.

The concrete inheritance

The _inherits function basically specifies the path for combining parasites, so let’s look at the details:

// demo_babel.js
var SubType =
  /*#__PURE__*/
  function (_SuperType) {
    _inherits(SubType, _SuperType);

    function SubType(name, age) {
      var _this;

      _classCallCheck(this, SubType);

      _this = _possibleConstructorReturn(this, _getPrototypeOf(SubType).call(this, name));
      _this.age = age;
      return _this;
    }

    _createClass(SubType, [{
      key: "sayHi",
      value: function sayHi() {
        console.log("Sub HI!", this.name, this.age);
      }
    }]);

    return SubType;
  }(SuperType);
Copy the code

The idea of parasitic combination mainly consists of two parts: 1. Construct itself by borrowing the parent constructor; 2. Get prototype relationships right. The _inherits function handles 2. The example in the previous article, superclass.call, shifts the construction process to subclass instances, while Babel does it more subtly. Take a closer look:

_this = _possibleConstructorReturn(this, _getPrototypeOf(SubType).call(this, name));
Copy the code

Call (this, name) uses call as the prototype for SubType. Remember that the last step in _inherits is when the prototype for SubType is SuperType. This step can be considered a SuperType. Call the process of the _possibleConstructorReturn function examined the second parameter, if the second parameter is an object or function, then the parent class construction method has covered instance; The _assertThisInitialized function is then used to check whether this was instantiated by its parent. According to this logic, the _getPrototypeOf(SubType).call(this, name) step theory will instantiate the correct parent of this, but this check is not unreasonable.

If we comment out super(name), maybe your editor will give you an error, but this code will translate correctly and look like this:

var SubType =
/*#__PURE__*/
function (_SuperType) {
  _inherits(SubType, _SuperType);

  function SubType(name, age) {
    var _this;

    _classCallCheck(this, SubType);

    // super(name);
    _this.age = age;
    return _possibleConstructorReturn(_this);
  }

  _createClass(SubType, [{
    key: "sayHi",
    value: function sayHi() {
      console.log("Sub HI!", this.name, this.age);
    }
  }]);

  return SubType;
}(SuperType);
Copy the code

This also explains why there is a _this in there to indicate that this was initialized by the parent class. We can simply think of _this as a wait state for a subclass instance, and this must go through the superclass instance for _this to actually take effect.

conclusion

Class +extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends Prototypes and prototype objects (__proto__ and Prototype).

JavaScript added the class and extends keywords in ES6 as syntactic sugar for types. The implementation of Babel may handle instance methods more subtly. When it comes to inheritance, we first sort out various inheritance modes logically, and the core is the problem of prototype chain. The prototype relationship is established through the parent class instance, the prototype relationship is established through object.create, and so on. The inheritance of Babel translation takes the form of a parasitic combination, which is reflected in the implementation of superclass.call. Babel also adds the necessary checks, namely whether super() is called; Create creates relationships directly while taking care of constructor’s characteristics. In addition, the prototype relationship between the two constructors is established, and then the parent constructor is implicitly called by this relationship. In practice, the static method of the parent can be directly inherited.

Welcome to my personal blog to browse more articles and put forward more valuable opinions