Asked me before if ES5 inheritance and ES6 inheritance have what distinction, I will be confident that there is no difference, but was a syntactic sugar, at best, that is, write anything, but now I would pretend to think about it, then say it is syntactic sugar, but it’s a little village, so what is the difference between specific, don’t go away, below is more fascinating!

This article will first review the implementation of ES5’s parasitic combinatorial inheritance, then take a look at how ES6 is written, and finally see what the differences are based on Babel’s compilation results.

ES5: Parasitic combinatorial inheritance

Js has many kinds of inheritance methods, such as familiar prototype chain inheritance, structure inheritance, combination inheritance, parasitic inheritance, etc., but these more or less have some shortcomings, so I think we only need to remember a kind of inheritance, that is parasitic combination inheritance.

First, we need to clarify exactly what inheritance is to inherit, there are three parts, one is the instance property/method, two is the prototype property/method, three is the static property/method, let’s look at separately.

Let’s look at the parent function we want to inherit:

/ / parent class
function Sup(name) {
    this.name = name// Instance properties
}
Sup.type = 'lunch'// Static attributes
// Static method
Sup.sleep =  function () {
    console.log(` I slept inThe ${this.type}Sleep `)}// Instance method
Sup.prototype.say = function() {
    console.log('my name is' + this.name)
}
Copy the code

Inherit instance properties/methods

To inherit an instance attribute/method, you obviously need to execute Sup and change its this pointer, using either call or apply:

/ / subclass
function Sub(name, age) {
    // Inherits the instance attributes of the parent class
    Sup.call(this, name)
    // Own instance properties
    this.age = age
}
Copy the code

The reason for this is another classic interview question: What does the new operator do?

1. Create an empty object

2. Point the __proto__ attribute of this object to sub. prototype

3. Set this in the constructor to point to the new object, and execute the constructor.

4. Return the object

So the this of sup.call (this) refers to the newly created object, and the instance attributes/methods of the parent class are added to the object.

Inheriting stereotype properties/methods

We all know that if an object doesn’t have a method of its own, it will look for it on its constructor’s prototype object, which is __proto__, and if it doesn’t find it, it will look for it on its constructor’s __proto__, and so on, layer by layer, the legendary prototype chain, Sub.prototype = sub. prototype = sub. prototype = sub. prototype = sub. prototype = sub. prototype

1. UseObject.create

Sub.prototype = Object.create(Sup.prototype)
Sub.prototype.constructor = Sub
Copy the code

2. Use__proto__

Sub.prototype.__proto__ = Sup.prototype
Copy the code

3. Borrow intermediate functions

function Fn() {}
Fn.prototype = Sup.prototype
Sub.prototype = new Fn()
Sub.prototype.constructor = Sub
Copy the code

We can override the inherited Say method, and then call the parent Say method in this method:

Sub.prototype.say = function () {
    console.log('hello')
    // Call the prototype method of the parent class
    // this.__proto__ === sub. prototype, sub.proto__ === sup.prototype
    this.__proto__.__proto__.say.call(this)
    console.log(This year `The ${this.age}At the age of `)}Copy the code

Inherit static properties/methods

Inherit the attributes and methods of the Sup function itself. This is as simple as iterating through the enumerable attributes of the parent class and adding them to the subclass:

Object.keys(Sup).forEach((prop) = > {
    Sub[prop] = Sup[prop]
})
Copy the code

ES6: Use class inheritance

Next we use the ES6 class keyword to implement the above example:

/ / parent class
class Sup {
    constructor(name) {
        this.name = name
    }
    
    say() {
        console.log('my name is' + this.name)
    }
    
    static sleep() {
        console.log(` I slept inThe ${this.type}Sleep `)}}// static can only set static methods, can not set static properties, so you need to add it to the Sup class
Sup.type = 'lunch'
// In addition, the prototype property can not be set in the class, you need to manually set to prototype, such as sup.prototype. XXX = 'XXX'.

// Subclass inherits parent class
class Sub extends Sup {
    constructor(name, age) {
        super(name)
        this.age = age
    }
    
    say() {
        console.log('hello')
        super.say()
        console.log(This year `The ${this.age}At the age of `)
    }
}
Sub.type = 'lazy'
Copy the code

Use Babel to compile this code back into ES5 syntax and see if it differs from what we wrote. Since the compiled code is over 200 lines, we can’t post them all at once. Let’s start with the parent class:

The compiled parent class

/ / parent class
var Sup = (function () {
  function Sup(name) {
    _classCallCheck(this, Sup);

    this.name = name;
  }

  _createClass(
    Sup,
    [
      {
        key: "say".value: function say() {
          console.log("My name is" + this.name); },},], [{key: "sleep".value: function sleep() {
          console.log("\u6211\u5728\u7761".concat(this.type, "\u89C9")); }},]);returnSup; }) ();// static Can only set static methods, cannot set static properties

Sup.type = "Noon"; // Subclass inherits parent class
// If we set the prototype property with sup.prototype. XXX = 'XXX', then there is no difference between the compiled property and the static property
Copy the code

Sup calls _classCallCheck(this, Sup). Sup calls _classCallCheck(this, Sup).

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

The instanceof operator is used to check if the prototype property of the function on the right is present in the prototype chain of the object on the left. In short, it can tell if an object is an instanceof a constructor. If not, the error message is that you cannot call a class as a function. Here we find the first difference:

Difference 1: An ES5 constructor is a normal function that can be called with new or directly, whereas an ES6 class cannot be called as a normal function and must be called with the new operator

Continuing with the self-executing function, a _createClass method is called next:

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}
Copy the code

This method takes three arguments: a constructor, a stereotype method, and a static method (note that stereotype and static properties are not included). The last two are arrays, each of which represents a method object.

function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    // Set this property to enumerable, false for.. In, object. keys Cannot traverse the property
    descriptor.enumerable = descriptor.enumerable || false;
    // This attribute can be modified and deleted by default
    descriptor.configurable = true;
    // When set to true, the value of this attribute can be changed by the assignment operator
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor); }}Copy the code

You can see that it sets the prototype and static methods using the Object.defineProperty method, and Enumerable defaults to false, which brings us to the second difference:

Difference between 2: ES5 prototype method and static method the default can be enumerated, and the class’s default cannot be enumerated, if want to obtain an enumeration properties can use Object. GetOwnPropertyNames method

Now look at the compiled code for the subclass:

Compiled subclasses

// Subclass inherits parent class
var Sub = (function (_Sup) {
  _inherits(Sub, _Sup);

  var _super = _createSuper(Sub);

  function Sub(name, age) {
    var _this;

    _classCallCheck(this, Sub);

    _this = _super.call(this, name);
    _this.age = age;
    return _this;
  }

  _createClass(Sub, [
    {
      key: "say".value: function say() {
        console.log("Hello");

        _get(_getPrototypeOf(Sub.prototype), "say".this).call(this);

        console.log("\u4ECA\u5E74".concat(this.age, "\u5C81")); }}]);return Sub;
})(Sup);

Sub.type = "Lazy";
Copy the code

Var inherits(Sub, _Sup); var inherits(Sub, _Sup); var inherits(Sub, _Sup);

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

This method checks whether the parent class is valid, and then sets the prototype of the subclass using object. create. This method is the same as before, but today I discovered that Object.create has a second argument, which must be an Object. The object’s own enumerable properties (that is, properties defined by itself, rather than enumerated properties in its stereotype chain) add the specified property values and corresponding property descriptors to the newly created object.

The conclusion of this method reveals a third difference:

Prototype = Function. Prototype = Function. Prototype = Function.

ES6: sub.__proto__ === Sup

ES5: sub.__proto__ === function.prototype

To see why this is so, look at what the _setPrototypeOf method does:

function _setPrototypeOf(o, p) {
    _setPrototypeOf =
        Object.setPrototypeOf ||
        function _setPrototypeOf(o, p) {
            o.__proto__ = p;
            return o;
        };
    return _setPrototypeOf(o, p);
}
Copy the code

As you can see, this method sets sub.__proto__ to Sup, which also completes the inheritance of static methods and properties, because functions are objects, and properties and methods that do not have their own properties are also looked up along the __proto__ chain.

The _inherits method is followed by calling a _createSuper(Sub) method.

function _createSuper(Derived) {
    return function _createSuperInternal() {
        // ...
    };
}
Copy the code

This function takes the subclass constructor and returns a new function. Let’s skip to the subclass constructor definition:

function Sub(name, age) {
    var _this;

    // Check if it is a normal function call, if it is, throw an error
    _classCallCheck(this, Sub);

    _this = _super.call(this, name);
    _this.age = age;
    return _this;
}
Copy the code

The new operator does what it does. We know that we implicitly create an object and refer to this in the function. If we don’t explicitly specify what the constructor returns, _this is the result of _super, which is the new function returned by _createSuper:

function _createSuper(Derived) {
    // _isNativeReflectConstruct checks if the reflect. construct method is available
    var hasNativeReflectConstruct = _isNativeReflectConstruct();
    return function _createSuperInternal() {
        The // _getPrototypeOf method is used to get the Derived prototype, which is Derived.__proto__
        var Super = _getPrototypeOf(Derived),
            result;
        if (hasNativeReflectConstruct) {
            // NewTarget === Sub
            var NewTarget = _getPrototypeOf(this).constructor;
            // Reflect. Construct: result = new Super(... Proto__ === newTarget. prototype; otherwise, it defaults to super. prototype
            result = Reflect.construct(Super, arguments, NewTarget);
        } else {
            result = Super.apply(this.arguments);
        }
        return _possibleConstructorReturn(this, result);
    };
}
Copy the code

Super stands for sub. __proto__. According to the previous inheritance operation, we know that the __proto__ of the subclass refers to the parent class, which is Sup. And this instance’s __proto__ refers back to sub. prototype, which I have to say is amazing.

We’re not going to worry about the degradation, so we’re going to return an instance object of the parent.

Back to the Sub constructor, _this points to the instance object created from the parent class. Why do we do this? This is actually the fourth and most important difference:

Difference 4: ES5 inheritance essentially creates the subclass’s instance object this, and then executes the parent class’s constructor to add instance methods and attributes to it (it doesn’t matter if you don’t). In ES6, the inheritance mechanism is completely different. In essence, we create an instance object of the parent class this (of course its __proto__ refers to the prototype of the child class), and then modify this with the constructor of the child class.

This is why you must call super in the constructor function to use class inheritance, since subclasses do not have their own this, and it is obvious why this cannot be accessed before super is called, since this has a value after super is called.

The last part of a subclass’s self-executing function is to set the prototype method and static method for it.

function say() {
    console.log("Hello");

    _get(_getPrototypeOf(Sub.prototype), "say".this).call(this);

    console.log("\u4ECA\u5E74".concat(this.age, "\u5C81"));
}
Copy the code

If you forget what the original function looks like before compiling, look at this:

say() {
    console.log('hello')
    super.say()
    console.log(This year `The ${this.age}At the age of `)}Copy the code

In ES6 class, super has two meanings. When used as a function call, super represents the constructor of the parent class and can only be called from within constructor. When used as an object, super refers to the prototype object of the parent class. So _get(_getPrototypeOf(sub.prototype), “say”, this).call(this) It’s similar to the ES5 version we started writing, but the class syntax is obviously much simpler.

So far, we have analyzed the compiled code, but there is actually a difference that I don’t know if you have noticed, that is why we use self-executing functions. One is to encapsulate some variables, and the other is actually because of the fifth difference:

Difference 5: Class does not have variable promotion, so the superclass must be defined before the subclass

If you try putting the parent class after the child class, you will get an error. You may feel that you can use the function expression directly to achieve the same effect, otherwise:

/ / complains
var Sub = function(){ Sup.call(this)}new Sub()
var Sup = function(){}

// No error will be reported
var Sub = function(){ Sup.call(this)}var Sup = function(){}
new Sub()
Copy the code

But Babel compiles an error whenever you instantiate a child class after its parent class.

conclusion

This article summarizes five differences between ES5 and ES6 inheritance, and possibly a few others, by analyzing Babel compiled code.

To learn more about classes, see this tutorial class Inheritance.

Sample code at github.com/wanglin2/es… .