React blog: Es6 class: Function: Es6 Class: Function: Es6 Class: Function: Es6

We come to this blog post with two questions:

  • How is class implemented with function
  • What is the difference between new class and new function

How to implement class with function

Prototype chain

As we all know, class is actually a syntactic sugar for function, which is based on prototype chains, so if you want to understand this blog post, you should first read my previous blog on prototype chains: Using formulas to explain prototype chains

Use Babel to convert classes to ES5 code

We can write a piece of class code, use the online Babel tool to translate it into ES5 code, and then analyze it step by step.

So let’s first write a little bit of class code

class Person{
   constructor(name, age){
     this.name = name
     this.age = age
    }

    static type = 'being'

  sayName (){
    return this.name
    }

  static intro(){
    console.log("")}}class Men extends Person{
    constructor(name, age){
        super(name, age)
      this.gender = 'male'}}Copy the code

The converted function is too long, SO I won’t post it completely here. If you want to see the complete version, you can go to the link above to see it yourself. We now start to analyze the transformed function step by step.

Auxiliary function

_inherits

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);
}
Copy the code

The name of this function is used for inheritance, which is called when extends extends.

This function first checks whether the parent meets the criteria, and if not, throws an exception.

This is followed by calling the Object.create method, which does the following:

The ** object.create ()** method creates a new Object, using an existing Object to provide the __proto__ of the newly created Object.

In other words:

subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { value: subClass, writable: true.configurable: true}});Copy the code

Prototype creates a new object with its __proto__ pointing to superclass. prototype. Then assign this object to subclass.prototype.

Subclass.prototype. __proto__ = superclass.prototype

In this way, the subClass prototype chain has the superclass prototype.

And then the last step is _setPrototypeOf, so let’s move on to what does this function do

_setPrototypeOf

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

This function is also used to construct the prototype chain, which is the core purpose of this sentence

o.__proto__ = p
Copy the code

I’m going to combine that with an auxiliary function

_setPrototypeOf(subClass, superClass)
Copy the code

The result is subclass.__proto__ = superClass

_isNativeReflectConstruct

function _isNativeReflectConstruct() {
  if (typeof Reflect= = ="undefined" || !Reflect.construct) return false;
  if (Reflect.construct.sham) return false;
  if (typeof Proxy= = ="function") return true;
  try {
    Boolean.prototype.valueOf.call(
      Reflect.construct(Boolean[],function () {}));return true;
  } catch (e) {
    return false; }}Copy the code

To understand this code, you need to understand what Reflect is. What is reflect.Construct

First Reflect is also a new syntax for ES6. What can be done in MDN is not our current focus, we will only focus on one point for now:

Reflect.construct(target, argumentsList[, newTarget\])

New to the constructor is equivalent to executing a new target(… The args).

Construct = reflect.construct = reflect.construct = reflect.construct = reflect.construct = reflect.construct = reflect.construct = reflect.construct = reflect.construct = reflect.construct

_getPrototypeOf

function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf
    ? Object.getPrototypeOf
    : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
      };
  return _getPrototypeOf(o);
}
Copy the code

This function is the next stereotype in the chain of stereotypes that takes the parameter passed in, or the object that it directly inherits.

_typeof

function _typeof(obj) {
  "@babel/helpers - typeof";
  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);
}

Copy the code

This code first creates the function variable _typeof via if-else. Since this is ES5 code, there is no const or let, and this method of direct variable assignment is actually the same as var, so there is variable promotion

The result is that a function is declared in function scope, assigned to _typeof, and then called to return the result.

I’m going to leave a little bit of a hole here as to why this is happening, but let’s just leave it at that for browser compatibility.

_possibleConstructorReturn

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

Void 0 === undefined is true.

If call is an object or a function, call is returned.

If not, check if self is undefined, throw an exception if it is, and return self if it is not

_createSuper

function _createSuper(Derived) {
  var hasNativeReflectConstruct = _isNativeReflectConstruct();
  return function _createSuperInternal() {
    var Super = _getPrototypeOf(Derived),
      result;
    if (hasNativeReflectConstruct) {
      var NewTarget = _getPrototypeOf(this).constructor;
      result = Reflect.construct(Super, arguments, NewTarget);
    } else {
      result = Super.apply(this.arguments);
    }
    return _possibleConstructorReturn(this, result);
  };
}
Copy the code

Okay, so all of the helper functions above, this is where they end up.

Let’s take a look at what this function does:

  • First determines the current execution environment can Reflect. Call the construct, if can hasNativeReflectConstruct is true.
  • The result of this function actually returns another function, _createSuperInternal, that uses the outer arguments, so this is a closure.
  • So let’s see what happens in this closure:
    • Get the stereotype of Derived as Super
    • If the current environment can Reflect call. The construct (hasNativeReflectConstruct is true)
    • var NewTarget = _getPrototypeOf(this).constructor; Gets the constructor of the current function executor’s prototype chain and assigns it to NewTarget.
    • result = Reflect.construct(Super, arguments, NewTarget); Call new Super with arguments and create an object whose constructor is NewTarget
    • If hasNativeReflectConstruct is false, the apply directly on this call, this way of inheritance is actually use the constructor to inherit the inheritance way, there are many other ways of inheritance, interested can go to my another blog: Kingworker. Cn/javascript -…
    • Final call _possibleConstructorReturn (this, the result), as a result, if the result if the object or function, it returns the result, otherwise, if this is not a undefined, it returns this and do not conform to throw an error.

_classCallCheck

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

This is a simple function to check that a class cannot be called like a normal function, such as class Persion. You cannot directly Persion().

_defineProperties

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 _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;
}
Copy the code

What these two functions do together is constantly define new properties on target

_createClass

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

Combined with the _defineProperties function above, this function takes three arguments. The first argument is a constructor, the second argument is an array of all normal attributes, and the third argument is an array of all static attributes.

  • Define common attributes in class on the constructor stereotype so that when we create a new instance, we can find these common attributes in the stereotype chain of the instance

    Those of you who read my previous blog about prototype chains may remember my formula:

    a = new A() => a.__proto__ = A.prototype

  • Define static properties in class on constructors so that we can point to static methods directly on constructors.

Dynamically generated code

Ok, so with these helper functions above, I can see how the class we defined at the beginning can be implemented using these helper functions

var Person = /*#__PURE__*/ (function () {
  function Person(name, age) {
    _classCallCheck(this, Person);

    this.name = name;
    this.age = age;
  }

  _createClass(
    Person,
    [
      {
        key: "sayName".value: function sayName() {
          return this.name; }}], [{key: "intro".value: function intro() {
          console.log(""); }}]);returnPerson; }) (); _defineProperty(Person,"type"."being");

var Men = /*#__PURE__*/ (function (_Person) {
  _inherits(Men, _Person);

  var _super = _createSuper(Men);

  function Men(name, age) {
    var _this;

    _classCallCheck(this, Men);

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

  return Men;
})(Person);
Copy the code
  • First, Persion is defined as the return result of a function that executes immediately. This return result is also a function, so we know that Persion is actually a function. We still call this function Constructor Persion.

  • What does this Constructor Persion function do?

    • First, call_classCallCheck(this, Person);This is the helper function we just analyzed, ensuring that our Constructor Persion function is not called directly, but is placed after new as a Constructor.
    • Then add two attributes, name and age, to the instance
  • Call _createClass. As we’ve just analyzed, this function takes a constructor, an array of normal attributes, and an array of static attributes, so after this step, Persion. Prototype has a new attribute called sayName. There is a new static property on Persion called intro.

  • Finally return Constructor Persion

  • Because type is a static property of Class Persion, define type on Constructor Persion so that it can be found directly on the Constructor.

  • Men is also defined as the return result of an immediately executed function, which is also a function. We call this function Constructor Men.

  • We call _inherits(Men, _Person), which uses the _inherits function

    subClass.prototype.__proto__ = superClass.prototype

    subClass.__proto__ = superClass

    Put it here, which means

    Men.prototype.__proto__ = _Person.prototype

    Men.__proto__ = _Person

    This completes the inheritance of the prototype chain

    One thing to note here is that this is the function Men declared below, not the outer Men, because the function declaration is promoted

  • var _super = _createSuper(Men)

    We just analyzed the _createSuper function, which returns another function that takes men.__proto__ as Super and then calls Super in the context of the function caller. _inherits(Men, _Person) already makes men.__proto__ = _Person.

    So the result of this step is to return a function that calls Persion in the context of the caller

  • Second, define Constructor Men (although the code order is different, function declarations promote variables). What does this Constructor Men do?

    • Also check if it is directly called to Men.
    • Calling _super, which is Persion on an instance of Men, is typical of borrowed constructor inheritance.
    • Mount the gender attribute on the Men instance.
  • Returns the Constructor Men

Let’s finally analyze the results:

  • First we have two functions, Persion and Men, both of which can only be called by new, otherwise an exception will be thrown.
  • Second, both constructors define declared static properties on their own, and constructor stereotypes define ordinary properties.
  • Men.prototype.__proto__ = _Person.prototype
  • Men.__proto__ = _Person
  • The constructor for Men is inherited by calling the Persion constructor on the Men instance.

New function and new class

There is no difference, just a few more built-in checks to help you do the inheritance of the prototype chain.

Class essentially returns a function.