It is well known that JS is used in regular development languages. At the top of the tech disdain chain. JS is bad, no. Appearance of Node. Indicates that JS has a unified front and back end trend. (This is just a small brother of a humble opinion, do not spray) or humble to say, JS can be at the back end can also show a fist.

One of the things that other OOP languages have a problem with is that JS is based on Prototype inheritance. As a humble person who has worked with C++ and JAVA and used those languages on real projects, I first met prototype with Nick young’s face full of question marks.

But things have gotten a little better since ES6, which also has class/extends syntax implementations. JS in many OOP languages, instantly stand up, there is no.

In fact, as a front-end developer who has worked with other OOP languages, I like to use class to define the structure of objects and extends to implement inheritance. But in almost any documentation about ES6 features, you’ll definitely see it

The ES6classYou can think of it as just a syntactic candy that does most of what ES5 does

In fact, in development, class is used directly to define the object structure and extends is used to implement inheritance. Very common, very convenient.

Now that they say it, it’s a candy. But as a qualified developer. Be willing to get to the bottom of it. So, I’m going to use my poor code to take a closer look at this layer of sugar.

The following methods are based on the analysis of class after it has been desugared. Take a look at how es6-class does Prototype “saccharification” gracefully. It’s great, but don’t forget to come back to this. The plot is pretty red, too.

ES5 is based on prototype inheritance

When we talk about inheritance, we need to be very clear. ES5 class = constructor + Prototpye. So there are two directions of inheritance based on this.

  1. Inherits instance properties and methods from constructors
  2. Inherits properties and methods from the prototype object

There are many ways to implement INHERITANCE in ES5: for example, stereotype chain inheritance (stereotype inheritance), constructor inheritance (instance attributes), combinatorial inheritance, primitive inheritance, parasitic inheritance, and parasitic combinatorial inheritance. Among them, we use combinatorial inheritance (prototype chain + constructor inheritance) to briefly explain the implementation principle.

Prototype chain inheritance

Inherits properties and methods from the prototype object

Assign an instance of a type to a stereotype of another constructor.

A subclass is a prototypical way of inheriting from a parent class.

function SuperObj(){
    this.objName = 'super';
}

SuperObj.prototype.getName = function(){
    return this.objName;
}

function SubObj(){
    this.objName = 'sub';
}
// Assign an instance of the parent to the prototype of the target class
SubObj.prototype = new SuperObj();

var instance = new SubObj();

// Inherits the parent class's prototype method
instance.getName() //super

instance  instanceof SubObj //true
instance instanceof SuperOjb //true

Copy the code

Constructor inheritance

Inherits instance properties and methods from constructors

First, constructor inheritance is just inheritance of properties in the target constructor, not true Prototype-based inheritance. An instanceof a subclass that uses instanceof returns false

Subclasses inherit the attributes and methods of their parent class

function SuperObj(){
    this.nameArr = ['north Chen'.'the south stand'];
}
// Call the superclass constructor in the subclass constructor
function SubObj(){
    SuperOjb.call(this)}var instance1 = new SubObj();
instance1.nameArr.push('hello'); //[' Bei Chen ',' Nan Zhen ',' Hello ']

var instance2  = new SubObj();

instance2.nameArr //[' Bei Chen ',' Nan Zhen ']

instance1 instanceof SuperObj //false 


Copy the code

Combination of inheritance

Combinatorial inheritance is constructor inheritance + prototype chain inheritance. That is to use the stereotype chain to achieve inheritance of stereotype methods, using constructors to achieve inheritance of instance properties.

Since ES5 implements a complete class, the constructor + Prototype is required.

function SuperObj(allname){
    this.nameArr = ['north Chen'.'the south stand'];
    this.allName =allname;
}

SuperObj.prototype.getAllName = function(){
    return this.allName;
}

Constructor inherits the properties and methods of the target constructor
function SubObj(allname,age){  
    SuperObj.call(this,allname);
    this.age = age;
}

// Prototype chain inheritance
SubObj.prototype = new SuperObj('north Chen');

var instance1 = new SubObj('instance1'.1);

var instance2 = new SubOjb('instance2'.2);

instance1 instanceof SuperObj //true
instance2 instanceof SuperObj //true

instance1.getAllName() //instance1
instance2.getAllName()//instance2

Copy the code

ES6 leverages the “saccharification” inheritance of extends

The simplest inheritance

Talk is cheap,show you the code


class A {}class B extends A{}Copy the code

This is the simplest inheritance. Or strictly speaking, B made A copy of A. Because A does not have any instance properties or methods, nor does it have any stereotype properties.

But we can look at the code after the sugar is removed.


"use strict";

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

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

function _instanceof(left, right) {
  if( right ! =null &&
    typeof Symbol! = ="undefined" &&
    right[Symbol.hasInstance]
  ) {
    return!!!!! right[Symbol.hasInstance](left);
  } else {
    return left instanceofright; }}function _classCallCheck(instance, Constructor) {
  if(! _instanceof(instance, Constructor)) {throw new TypeError("Cannot call a class as a function"); }}var A = function A() {
  _classCallCheck(this, A);
};

var B =
  /*#__PURE__*/
  (function(_A) {
    _inherits(B, _A);

    function B() {
      _classCallCheck(this, B);

      return _possibleConstructorReturn(
        this,
        _getPrototypeOf(B).apply(this.arguments)); }return B;
  })(A);

Copy the code

Let’s analyze a wave.

var A = function A() {
  _classCallCheck(this, A);
};
Copy the code

There is no need to say more about this. It simply defines an empty constructor.

The most important thing is the following code: the first thing that pops into view is an IIFE that receives the constructor A that has just been defined.

So I’m going to add the corresponding comment here.

(function(_A) {
    // Inherits properties and methods from the prototype object
    _inherits(B, _A);
    
    function B() {
      _classCallCheck(this, B);
      // Inherits instance properties and methods from the constructor
      return _possibleConstructorReturn(
        this,
        _getPrototypeOf(B).apply(this.arguments)); }return B;
  })(A);

Copy the code

Through the code we see _inherits (B, _A) applied to inherit a prototype object properties and methods, and _possibleConstructorReturn is inherited in the constructor instance attributes and methods. So it fits nicely with what we said above.

Then we continue to analyze why:

Inherits properties and methods on stereotype objects (_inherits)

Talk is cheap,show you the code:

//_inherits(B, _A)
function _inherits(subClass, superClass) {
    // Type the superClass
  if (typeofsuperClass ! = ="function"&& superClass ! = =null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  // Subclass prototype inherits prototype from its parent
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { value: subClass, writable: true.configurable: true}});// Subclasses are function objects built from the parent class and need to specify the object's __proto__
  if (superClass) _setPrototypeOf(subClass, superClass);
}
Copy the code

Note: One thing to Note is that in most browsers’ ES5 implementations, each object has a __proto__ attribute that points to the prototype attribute of the corresponding constructor. Class is the syntactic sugar of the constructor and has both the Prototype and __proto__ attributes, so there are two inheritance chains. (1) The __proto__ attribute of a subclass, which indicates the constructor’s inheritance, always points to the parent class. (2) The prototype attribute’s __proto__ attribute, indicating method inheritance, always points to the prototype attribute of the parent class.

Instance attributes and methods of inheriting the constructor (_possibleConstructorReturn)

/ * _possibleConstructorReturn (this, / / to subclass constructor / / _getPrototypeOf (B) is used to obtain the specified object parent _getPrototypeOf (B). Apply (this, arguments) ); * /
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;
}

Copy the code

In _possibleConstructorReturn call, actually handle the constructor of a class instance properties and methods of inheritance. Constructor needs _getPrototypeOf(B).apply(this, arguments) to copy the attributes and methods of the parent class into the new constructor. Implement inheritance.

Note: _getPrototypeOf(B). Apply (this, arguments) ¶ If not, is directly _possibleConstructorReturn (this)

Summarize the main points

The ES6extends implementation continues to be based on ES5 composite inheritance (constructor inheritance + stereotype chain inheritance)

  1. _inherits(B, _A)implementationPrototype chainThe inheritance of
  2. In the constructor of a subclass_possibleConstructorReturnimplementationThe constructorThe inheritance of

Complex class

The example above is an empty class A inherited by B. B essentially copies A, using the syntax of extends. Let’s look at the basic syntax implementation.

ES6 sample

Now let’s build a more general class: inheritance with instance methods

Talk is cheap ,show you the code:

class A {
  static name = 'superClass'
  constructor(x,y){
    this.x =x;
    this.y =y; }}class B extends A{
  	
	constructor(x,y,z){
    	super(x,y);
		this.y = y; }}Copy the code

ES5 off sugar

"use strict";

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

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

function _instanceof(left, right) {
  if( right ! =null &&
    typeof Symbol! = ="undefined" &&
    right[Symbol.hasInstance]
  ) {
    return!!!!! right[Symbol.hasInstance](left);
  } else {
    return left instanceofright; }}function _classCallCheck(instance, Constructor) {
  if(! _instanceof(instance, Constructor)) {throw new TypeError("Cannot call a class as a function"); }}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 A = function A(x, y) {
  _classCallCheck(this, A);

  this.x = x;
  this.y = y;
};

_defineProperty(A, "name"."superClass");

var B =
  /*#__PURE__*/
  (function(_A) {
    _inherits(B, _A);

    function B(x, y, z) {
      var _this;

      _classCallCheck(this, B);

      _this = _possibleConstructorReturn(
        this,
        _getPrototypeOf(B).call(this, x, y)
      );
      _this.y = y;
      return _this;
    }

    return B;
  })(A);


Copy the code

After looking at the code, the implementation process is basically the same as B inheriting an empty class A. Are _inherits () implementation prototype inheritance, _possibleConstructorReturn () constructor of inheritance.

But there’s an inner _this.

Here’s an extra point: ES5 inheritance essentially creates the subclass’s instance object this, and then adds the Parent class’s methods to this (parent.apply (this)). ES6 has a completely different inheritance mechanism, essentially adding the properties and methods of the superclass instance object to this (so the super method must be called first), and then modifying this with the constructor of the subclass.

That is, if you want to call this in a subclass constructor, you must first call super().