What is theclass

As we all know, JavaScript has no classes and classes are just syntactic sugar. This article aims to clarify what we mean by syntactic sugar.

ES6ES5Writing contrast

class Parent {
    static nation = 'China'
    
    isAdult = true
    
    get thought() {
        console.log('Thought in head is translate to Chinese.')
        return this._thought
    }
    
    set thought(newVal) {
        this._thought = newVal
    }
    
    constructor(name) {
        this.name = name
    }
    
    static live() {
        console.log('live')
    }

    talk() {
        console.log('talk')}}Copy the code

This is a pretty complete notation, and we’re used to writing a class this easily, but what about the notation in ES5

function Parent(name) {
    this.name = name
    this.isAdult = true
}

Parent.nation = 'China'
Parent.live = function() {
    console.log('live')
}
Parent.prototype = {
    get thought() {
        return this._thought
    },
    set thought(newVal) {
        this._thought = newVal
    },
    talk: function() {
        console.log('talk')}}Copy the code

You can see it very clearly

  • ES6ParentOf the classconstructorAnd that corresponds toES5Constructor inParent;
  • Instance attributesnameisAdult, no matter inES6Which is written inES5In is still hanging onthisUnder;
  • ES6Through the keywordstaticModified static properties and methodsnationlive, are hung directly in the classParentOn;
  • It’s worth notingGetter and setter toughtAnd methodstalkIs hung on the prototype objectParent.prototypeOn the.

BabelHow is it compiled

We can see the compiled code by typing it into the Try It Out section of the Babel website. We will compile the code step by step and disassemble the Babel compilation process:

Process a

At this point, we only observe the compile-related results of the attribute, before compiling:

class Parent {
    static nation = 'China'
    
    isAdult = true
    
    constructor(name) {
        this.name = name
    }
}
Copy the code

The compiled:

'use strict'
  // Encapsulate the instanceof operation
  function _instanceof(left, right) {
    if( right ! =null &&
      typeof Symbol! = ='undefined' &&
      right[Symbol.hasInstance]
    ) {
      return!!!!! right[Symbol.hasInstance](left)
    } else {
      return left instanceof right
    }
  }
  // The ES6 class must be called with the new operation,
  // This method checks to see if the _instanceof method encapsulated above is called with the new operation
  function _classCallCheck(instance, Constructor) {
    if(! _instanceof(instance, Constructor)) {throw new TypeError('Cannot call a class as a function')}}// Wrapped object.defineProperty
  function _defineProperty(obj, key, value) {
    if (key in obj) {
      Object.defineProperty(obj, key, {
        value: value,
        enumerable: true.configurable: true.writable: true})}else {
      obj[key] = value
    }
    return obj
  }

  var Parent = function Parent(name) {
    // Check whether the call is made through the new operation
    _classCallCheck(this, Parent)
    // Initialize isAdult
    _defineProperty(this.'isAdult'.true)
    // Initializes name based on the input parameter
    this.name = name
  }
  // Initialize the static attribute nation
  _defineProperty(Parent, 'nation'.'China')
Copy the code

As you can see from the compiled code, Babel has wrapped some methods for rigor. One of the methods that may be a little confusing is the Symbol. HasInsance in the _instanceof(left, right) method, As you can see from the MDN and ECMAScript6 primer, this property can be used to customize the behavior of the Instanceof operator on a class. There is also a focus on the object _classCallCheck(instance, Constructor), which checks whether it is called by the new operation.

Process 2

Compile the front:

class Parent {
    static nation = 'China'
    
    isAdult = true
    
    get thought() {
        console.log('Thought in head is translate to Chinese.')
        return this._thought
    }
    
    set thought(newVal) {
        this._thought = newVal
    }
    
    constructor(name) {
        this.name = name
    }
    
    static live() {
        console.log('live')
    }

    talk() {
        console.log('talk')}}Copy the code

The compiled:

 'use strict'
  // Encapsulate the instanceof operation
  function _instanceof(left, right) {
    / /...
  }
  // The ES6 class must be called with the new operation,
  // This method checks to see if the _instanceof method encapsulated above is called with the new operation
  function _classCallCheck(instance, Constructor) {
    / /...
  }
  // Encapsulate Object.defineProperty to add attributes
  function _defineProperties(target, props) {
    / / traverse the props
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i]
      // Enumerable defaults to false
      descriptor.enumerable = descriptor.enumerable || false
      descriptor.configurable = true
      if ('value' in descriptor) descriptor.writable = true
      Object.defineProperty(target, descriptor.key, descriptor)
    }
  }
  // Add a prototype or static attribute to Constructor and return
  function _createClass(Constructor, protoProps, staticProps) {
    // If it is a stereotype property, add it to the stereotype object
    if (protoProps) _defineProperties(Constructor.prototype, protoProps)
    If it is static, add it to the constructor
    if (staticProps) _defineProperties(Constructor, staticProps)
    return Constructor
  }
  // Wrapped object.defineProperty
  function _defineProperty(obj, key, value) {
    / /...
  }

  var Parent =
    /*#__PURE__*/
    (function() {
      / / add getter/setter
      _createClass(Parent, [
        {
          key: 'thought'.get: function get() {
            console.log('Thought in head is translate to Chinese.')
            return this._thought
          },
          set: function set(newVal) {
            this._thought = newVal
          }
        }
      ])

      function Parent(name) {
        // Check whether the call is made through the new operation
        _classCallCheck(this, Parent)
        // Initialize isAdult
        _defineProperty(this.'isAdult'.true)
        // Initializes name based on the input parameter
        this.name = name
      }
      // Add talk and live methods
      _createClass(
        Parent,
        [
          {
            key: 'talk'.value: function talk() {
              console.log('talk'}}], [{key: 'live'.value: function live() {
              console.log('live'}}])return Parent
    })()
  // Initialize the static attribute nation
  _defineProperty(Parent, 'nation'.'China')
Copy the code

Compared to procedure 1, Babel generates an additional helper function for _defineProperties(target, props) and _createClass(Constructor, protoProps, staticProps). These are mainly used to add stereotype and static attributes, and both data descriptors and access descriptors can be controlled through object.defineProperty methods. It’s worth noting that every method in ES6’s class is an enumerable method. False), here is a small detail: If you use TypeScript, when you set target in compileOptions to ES5, you will find that compiled methods can be traversed through object.keys () but not through ES6.

conclusion

Babel parses through the AST abstract syntax tree and then adds the following

  • _instanceof(left, right) // Encapsulated instanceof operation
  • _classCallCheck(instance, Constructor) // Check whether the new operation is invoked
  • _defineProperties(target, props) // Encapsulates Object.defineProperty to add attributes
  • _createClass(Constructor, protoProps, staticProps) // Add a prototype or static attribute to Constructor and return
  • _defineProperty(obj, key, value) // // Encapsulated object.defineProperty

Five helper functions to add attributes and methods to the Parent constructor and convert the syntactic class to ES5 code.

What is theextends

Since ES6 doesn’t have classes, how does inheritance work? As you already know, extends, like class, is a syntactic sugar. Let’s break it down step by step.

ES5Parasitic combinatorial inheritance

A relatively perfect inheritance implementation is parasitic combinatorial inheritance. For ease of reading, I’ve attached the source code and schematic diagram again:

function createObject(o) {
    function F() {}
    F.prototype = o
    return new F()
}

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

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

Child.prototype = createObject(Parent.prototype)
Child.prototype.constructor = Child

var child = new Child('child')

Copy the code

ES6ES5Writing contrast

If we look at the inheritance implementation above, we can easily write two versions of the inheritance form

class Child extends Parent {
    constructor(name, age) {
        super(name); // Call parent class constructor(name)
        this.age = age; }}Copy the code
function Child (name, age) {
    Parent.call(this, name)
    this.age = age
}

Child.prototype = createObject(Parent.prototype)
Child.prototype.constructor = Child
Copy the code

BabelHow is it compiled

Some of the details

  • Subclasses must be inconstructorMethod callsuperMethod, or an error will be reported when creating a new instance. This is because subclasses don’t have their ownthisObject, but inherits from its parent classthisObject, and then process it. If you don’t callsuperMethod, subclasses don’t get itthisObject. For this reason, in the constructor of a subclass, there are only callssuperAfter that, it can be usedthisKeyword, otherwise an error will be reported.
  • inES6Static methods of the parent class can be inherited by subclasses.classSyntactic sugar as constructor, also hasprototypeProperties and__proto__Property, so there are two inheritance chains.

The build process

Again, we type the code into Try It Out on Babel’s website to see the compiled code:

'use strict'
  // Encapsulate typeof
  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)
  }
  // Call the parent class's constructor() and return the subclass's this
  function _possibleConstructorReturn(self, call) {
    if (
      call &&
      (_typeof(call) === 'object' || typeof call === 'function')) {return call
    }
    return _assertThisInitialized(self)
  }
  // Check if the subclass's super() is called
  function _assertThisInitialized(self) {
    if (self === void 0) {
      throw new ReferenceError(
        "this hasn't been initialised - super() hasn't been called")}return self
  }
  // Encapsulate getPrototypeOf
  function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf
      ? Object.getPrototypeOf
      : function _getPrototypeOf(o) {
          return o.__proto__ || Object.getPrototypeOf(o)
        }
    return _getPrototypeOf(o)
  }
  // Implement the inherited helper function
  function _inherits(subClass, superClass) {
    if (typeofsuperClass ! = ='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)
  }
  // Encapsulate setPrototypeOf
  function _setPrototypeOf(o, p) {
    _setPrototypeOf =
      Object.setPrototypeOf ||
      function _setPrototypeOf(o, p) {
        o.__proto__ = p
        return o
      }
    return _setPrototypeOf(o, p)
  }
  // Check whether the call is made through the new operation
  function _classCallCheck(instance, Constructor) {
    if(! _instanceof(instance, Constructor)) {throw new TypeError('Cannot call a class as a function')}}var Child =
    /*#__PURE__*/
    (function(_Parent) {
      // Inherit operations
      _inherits(Child, _Parent)

      function Child(name, age) {
        var _this

        _classCallCheck(this, Child)
        // Call the parent class's constructor() and return the subclass's this
        _this = _possibleConstructorReturn(
          this,
          _getPrototypeOf(Child).call(this, name)
        )
        // Initialize the subclass's own attributes based on the input parameter
        _this.age = age
        return _this
      }

      return Child
    })(Parent)
Copy the code

_inherits(subClass, superClass)

Let’s take a closer look at the details of the helper function that implements inheritance:

function _inherits(subClass, superClass) {
    Check that the object of extends's inheritance (the parent class) must be a function or null
    if (typeofsuperClass ! = ='function'&& superClass ! = =null) {
      throw new TypeError(
        'Super expression must either be null or a function')}// 2. Parasitic combinatorial inheritance similar to ES5, using object.create,
    // Set the __proto__ attribute of the protoclass to the prototype attribute of the parent class
    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: { value: subClass, writable: true.configurable: true}})// 3. Set the __proto__ attribute of the subclass to the parent class
    if (superClass) _setPrototypeOf(subClass, superClass)
  }
Copy the code

This method is divided into three main steps. In the second step, the implementation of inheritance via parasitic combinatorial inheritance adds an unenumerable attribute named constructor. Step 3 implements the second chain of stereotypes mentioned above so that static methods can be inherited.

_possibleConstructorReturn(self, call)

This helper function is used to implement the effect of super(), and for parasitic combinatorial inheritance it borroys the constructor inheritance. The difference is that this returns a this and assigns it to the subclass’s this. Details can be found in the ES6 series on how Babel compiles classes (below).

conclusion

Like class, Babel is parsed through an AST abstract syntax tree and then adds a set of helper functions, which I think can be divided into two categories:

  • _typeof(obj) // Encapsulated Typeof
  • _getPrototypeOf(o) // Encapsulated getPrototypeOf
  • _setPrototypeOf(o, p) // Encapsulated setPrototypeOf

This kind of functional auxiliary function for robustness is the second kind:

  • _assertThisInitialized(self) // Checks if the super() of the subclass is called
  • _possibleConstructorReturn (self, call) / / call the superclass constructor (), and return a subclass of this
  • _classCallCheck(instance, Constructor) // Check whether the new operation is invoked
  • _inherits(subClass, superClass) // Implements the inherits helper function

This process helper function for the main function enables a more complete parasitic composite inheritance.

Afterword.

Starting with Prototype, there are two parts to describe JavaScript prototypes from two perspectives.

  • Start with Prototype (part 1) – Illustrates ES5 inheritance
  • Start with Prototype — Class and extends in ES6

The resources

  • How does ES6 series Babel compile classes?
  • How does ES6 series Babel compile classes?