React has forgotten what a prototype chain is, has forgotten the original mind of the innocent years, front-end, write a lot of high quality code, should calm down, learn the basics, slowly review the following common sense points:

  • The constructor constructor
  • The prototype prototype chain
  • New what exactly happens to an object

The constructor constructor

Constructor is a property that every instance object has, and its real meaning lies in a pointer to the class that created the current instance object.

function Person() {}
let p = new Person();
/ / ƒ Person () {}
console.log(p.constructor);
Copy the code

As you can see from the console print, p.constructor points to the Person object, and the process of new will be explained later.

The constructor property value can be changed at any time. If it is not assigned, it defaults to the class that created the instance object, and if it is assigned, it points to the assigned value.

This property is rarely used in general development, so I’ll take a look at how Preact source code uses this property to solve business scenarios.

A Preact component can be created using a class that inherits or does not inherit a preact.componentparent class that has properties such as the Render method, or a stateless component (PFC) that is created using function. Here’s how Preact uses the constructor property.

  • Create a stateless component
// The stateless component created by the function
const Foo = (a)= > {
  return <div>Foo</div>;
};

// Common way to create container components
class App extends Preact.Component {
  render() {
    return (
      <div>
        <Foo />
      </div>); }}Copy the code
  • Babel transcoding
// Virtual DOM generation function after Babel transcoding
Preact.createElement(
  "div".null,
  React.createElement("p".null."hahahaha"),
  React.createElement(Foo, null));// This function returns a virtual DOM
var Foo = function Foo() {
  return Preact.createElement("div".null."Foo");
};
Copy the code
  • Type determination in the virtual DOM
if(typeof type === 'function'){
    ...
}
Copy the code

In the code above, the first argument in the preact. createElement method is type, where Foo is the function type.

  • FooTwo forms of the function
if (Foo.prototype && Foo.prototype.render) {
}
Copy the code

Foo () : Foo () : Foo () : Foo () : Foo () : Foo () : Foo () : Foo ();

Let’s take a look at the implementation of Preact.Com Ponent code:

function Component(props, context) {
  this.context = context;
  this.props = props;
  this.state = this.state || {};
  // ...
}
Object.assign(Component.prototype, {
  setState(state, callback) {},
  forceUpdate(callback) {},
  render() {}
});
Copy the code

Render (preact.component.component.render); render (preact.component.render); render (preact.component.render); render (preact.component.render);

let inst = new React.Component(props, context);
inst.constructor = Foo;
inst.render = function(props, state, context) {
  return this.constructor(props, context);
};
Copy the code

At first look at this few lines of code, there is a lot of detail involved.

First, it defines an instance object (inst) of the preact.componentclass. The constructor of this inst points to the preact.componentclass by default. Next, Assign the constructor property to inst, redirect to Foo, and finally add a render method to the instance object inst. The core of this method is that it executes this.constructor, which executes Foo. The Foo method returns a virtual DOM.

It now makes sense that stateless components will eventually also have a Render method that, when triggered, returns a virtual DOM or child component.

let inst = new React.Component(props, context);
inst.render = function(props, state, context) {
  return Foo(props, context);
};
Copy the code

You can use preact’s constructor to create an array queue recyclerComponents, which recycles and destroys components. It also makes its judgment using the constructor attribute:

if (recyclerComponents[i].constructor === Foo) {
  // ...
}
Copy the code

The prototype prototype chain

Every js object has a prototype object, the Prototype property.

function Person() {}
Copy the code

The prototype object for the Person object is the Person.prototype object:

The Person. Prototype object has those properties:

You can see that this object has two native properties by default: constructor and __proto__.

__proto__ is a built-in attribute that accesses [[Prototype]], which can be an object or null.

So what exactly is __proto__?

function Person() {}
let p1 = new Person();
Copy the code

The two red boxes in the figure show that p1.__proto__ and Person.prototype refer to the same object.

// true
p1.__proto__ === Person.prototype;
Copy the code

The Person object can inherit its methods and properties from the prototype object, so instances of the Person object can also access the properties and methods of the prototype object, but these properties and methods are not mounted on the instance object itself, but on the Prototype object’s constructor’s Prototype properties.

So where does Person. Prototype’s __proto__ point to?

P1.__proto__.__proto__ refers to Object. Prototype, p1.__proto__.__proto__ refers to null.

The construction of the prototype chain depends on the instance object__proto__Is not of the original objectprototype

ES6 set the method of obtaining the prototype:

// Set the attributes for the p1 prototype
Object.setPrototypeOf(p1, { name: "zhangsan" });

// zhangsan
console.log(p1.name);

// {name: "zhangsan"}
Object.getPrototypeOf(p1);
Copy the code

As you can see in the red box above, object. setPrototypeOf is the new syntax sugar, which is equivalent to assigning a value to p1.__proto__.

A simple case study:

Classic prototype chain diagram:

For example, how to implement Student inheritance from Person using native JS:

function Person(name) {
  this.name = name;
}
Person.prototype.getName = function() {
  return this.name;
};

function Student(name, age) {
  this.name = name;
  this.age = age;
}
Student.prototype.getInfo = function() {
  return `The ${this.name} and The ${this.age}`;
};
Copy the code

Implement inheritance by allowing instances of Student to access the attributes and methods of Person, as well as the method getName on the Person stereotype.

Let’s start with es6 inheritance:

class Person {
    public name: string
    constructor(name) {
        this.name = name
    }
    getName(){
        return this.name
    }
}

class Student extends Person {
    public age: number
    constructor(name, age) {
        super(name)
    }

    getAge() {
        return this.age
    }
}

let s = new Student('zhangsan'.20)

// zhangsan
s.name
// zhangsan
s.getName()
/ / 20
s.getAge()

Copy the code

So, how to do with native JS, the following step by step implementation.

  • callImplement function context inheritance
function Person(name) {
  this.name = name;
}
Person.prototype.getName = function() {
  return this.name;
};

function Student(name, age) {
  Person.call(this, name);
  this.age = age;
}
Student.prototype.getInfo = function() {
  return `The ${this.name} and The ${this.age}`;
};

let s = Student("zhangsan".20);

// zhangsan
s.name;

// error
s.getName();
Copy the code

The Call method only changes the this pointer in the Person function, but does not change its prototype, so it cannot access the prototype of the Person method.

  • Prototype chain implements prototype inheritance
function Person(name) {
  this.name = name;
}
Person.prototype.getName = function() {
  return this.name;
};

function Student(age) {
  this.age = age;
}
Student.prototype.getInfo = function() {
  return this.age;
};

Student.prototype = new Person("zhangsan");

let s = new Student(20);

// zhangsan
s.getName();
Copy the code

Student. Prototype sets the value of the parent class to the instance of Person. This makes it easy for Student to access the Person prototype, but there are problems with this. So this instance name = Zhangsan, this idea of OOP goes against this idea.

  • The combination of the above two approaches solves this problem perfectly
/** * Inherits the core method of functions */
function _extends(child, parent) {
  // Define an intermediate function and set its constructor
  function __() {
    this.constructor = child;
  }
  // The prototype of this function points to the prototype of the parent class
  __.prototype = parent.prototype;
  // The prototype of the subclass suffocates the instance object of this intermediate function
  child.prototype = new(4); }Copy the code

This _extends method is the core of the implementation. First, it defines an intermediate function without arguments and sets its constructor. The second is the use of prototype chains.

function Person(name) {
  this.name = name;
  this.getName1 = function() {
    console.log("Person".this.name);
  };
}

Person.prototype.getName = function() {
  console.log("Person prototype".this.name);
};

// This method must be called before subclass stereotypes are defined
_extends(Student, Person);

function Student(name, age) {
  this.age = age;
  Person.call(this, name);
}
Student.prototype.getInfo = function() {
  console.log("Student".this.age);
};

let s = new Student("zhangsan".12);

// Person prototype zhangsan
s.getName();
// Student 12
s.getInfo();
Copy the code

In this way, inheritance is simply implemented and multiple inheritance is supported

// Multiple inheritance
_extends(MidStudent, Student);
function MidStudent(name, age, className) {
  this.className = className;
  Student.call(this, name, age);
}

let mids = new MidStudent("lisi".16."class1");
// Person prototype lisi
mids.getName();
// Student 16
mids.getInfo();
Copy the code

There are many excellent cases on the Internet. This is the basis of JS. It is certainly more interesting than calling API all day long.

And finally, what happens to a new object

As the saying goes, if you don’t have a girlfriend, you can have a new one.

let p1 = new Person();
Copy the code

Step1, let the variable p1 point to an empty object

let p1 = {};
Copy the code

Step2, make p1 the object’s __proto__ attribute point to the prototype object of the Person object

p1.__proto__ = Person.prototype;
Copy the code

Step3, have p1 execute the Person method

Person.call(p1);
Copy the code

Now look at this process, is not very simple, is not a kind of suddenly enlightened feeling!

So how do you implement your own new?

/** * Con Target object * args parameter */
function myNew(Con, ... args) {
  // Create an empty object
  let obj = {};
  // Link to the prototype, obj can access properties in the constructor prototype
  obj.__proto__ = Con.prototype;
  // Bind this to implement inheritance, obj can access the properties in the constructor
  letret = Con.call(obj, ... args);// Return the object returned by the constructor first
  return ret instanceof Object ? ret : obj;
}
Copy the code

Let’s test it out:

function Person(name) {
  this.name = name;
}

Person.prototype.getName = function() {
  console.log(`your name is The ${this.name}`);
};

let p2 = myNew(Person, "lisi");

// your name is lisi
p2.getName();
Copy the code

Perfect!