Constructor

When we talk about prototypes, we have to mention constructors. What is a constructor? Simply put, it’s a function that creates an object. In typical OOP languages (such as Java), there exists the concept of a class, which is a template for an object and an object is an instance of a class, but the concept of a class was not introduced in JS prior to ES6. So objects are not created by classes, but by special functions called constructors that define objects and their characteristics. Constructors usually start with a capital letter and make sense when used with the new keyword. Here is a simple example

function Person(name,age){ this.name = name this.age = age this.say = function(){ console.log('my name is ' + this.name) }} const p1 = new Person(' zhang3 ',18) p1.say() // my name is zhang3Copy the code

As you can see, the constructor is no different from a normal function, but the new keyword makes it special by allowing it to create instances. So what does the new keyword do? Briefly, there are four points

  • 1. Create a new empty object in memory.
  • 2. Point the object prototype of the new object to the constructor’s prototype object.
  • 3. Make this point to the new object and execute the code inside the constructor to add properties and methods to the new object.
  • 4. Determine the return value of the constructor, and return the object if it is an object type, otherwise return an object of your own creation.

The specific code implementation of the new keyword will be discussed later, but not here

Two. Prototype objects

Why introduce the concept of stereotypes (prototype objects) when constructors can be used as templates to create multiple instance objects? The problem with constructors is that they waste memory. As in the example above, the constructor declares a say method inside. If I create multiple instance objects with this constructor, it means that each of them has a SAY method that does the same thing. The new keyword creates memory space for objects, meaning that these say methods do the same thing, but they belong to different instances, so they cannot be allocated memory space. This is the problem with constructors that waste memory. Hence the concept of a prototype.

What is a prototype?

JavaScript states that each constructor has a Prototype property that points to another object. All properties and methods of this object are owned by the constructor. This way, we can define those immutable methods directly on the Prototype object so that all instances created by the constructor can share them. So the role of prototypes can be boiled down to sharing methods. Again, we tried to mount the Say method on the Person prototype object

function Person(name,age){ this.name = name this.age = age } Person.prototype.say = function(name){ console.log('my name } const p1 = new Person(' 三',18) const p2 = new Person(' 三', 20) p1.say(p1.name) // My name is 三 P2.say (p2.name) // My name is Li SiCopy the code

As you can see, any instance created by Person can call the SAY method, so in general, methods that are common to objects are put on prototype objects. Based on this feature, one of the main applications of prototype objects is to extend the built-in object method. For example, adding a custom sum to an array. Array.prototype. XXX = function(){}

The say method is defined on the prototype object of Person. Why, or how, is it accessible to instances of Person? This brings us to another concept, object prototypes.

Object prototype

Any object created using a constructor will have a property _proto_ that points to the constructor’s Prototype object. That’s why the objects we create with constructors have access to properties and methods on the constructor’s prototype objects. In fact, the _proto_ object prototype is equivalent to the constructor’s prototype object prototype.

console.log(Person.prototype === p1.__proto__) // true 
Copy the code

As mentioned earlier, the constructor’s prototype refers to the prototype object. Similarly, the prototype object has a constructor property that refers back to the constructor itself. The significance of the constructor attribute is primarily to record which constructor the object refers to, which allows the prototype object to point back to the original constructor. How does that help?

In general, the methods of the object are set in the prototype object of the constructor. If we have methods for multiple objects, we can assign the prototype Object as an Object, but this overwrites the original constructor Object, so that the modified prototype Object constructor no longer points to the current constructor, but to Object instead. ** At this point, we can add a constructor pointing to the original constructor in the modified prototype object. Here is an example to illustrate:

function Person(name,age){ this.name = name this.age = age } Person.prototype = { Say :function(){console.log(' I can sing '); }} const p1 = new Person(' c9 ',18) p1.say()Copy the code

Prototype chain

Understanding the concept of prototype, prototype chain is not difficult to understand first we are going to explore the js member lookup mechanism.

  • 1. When accessing a member of an object, check whether the object itself has the member.
  • 2. If not, find its prototype object
  • 3. If not, find the prototype of the prototype Object.
  • 4. And so on until Object is found (NULL).
__proto__The point of object archetypes is to provide a direction, or a route, for the object member lookup mechanism, and that route is the prototype chain.

5. Inheritance

Now that you understand the concept of stereotypes and prototype chains, it’s time to talk about inheritance. Before ES6, there was no concept of class, so inheritance was implemented by combining constructor and prototype object inheritance.

Why this inheritance mechanism? As mentioned earlier, we generally declare public properties of objects in constructors and public methods of objects in prototype objects. From this, we have the idea of inheritance, we can use the constructor to inherit the attributes of the parent class, use the stereotype chain to inherit the methods of the parent class. The following is a specific explanation

1. Borrow constructors to inherit superclass attributes

This step is easy to understand. We simply repeat the operations of the parent constructor in the child constructor. But there’s a problem. As I mentioned earlier, the this in the constructor refers to the object instance that creates the function. Therefore, we cannot call the parent constructor directly from the child constructor, because this does not refer to the object instance of the child constructor. So we’re going to use the call method to change the direction of this.

function Father(name,age){ this.name = name; this.age = age; This.run = function(){console.log()}} function Son(name,age){Father(name,age)} const p1 = new Son('小 小 ',18) console.log(p1.name) // undefinedCopy the code

You can see that it is not possible to call the parent constructor directly in a subclass

Function Son(name,age){Father. Call (this,name,age)} const p1 = new Son('小明',18) console.log(p1.name) // 小明Copy the code

Now that inheritance of attributes is complete, let’s discuss inheritance of methods

2. Inherit superclass methods by borrowing archetypal objects

We already know that the constructor’s public methods are defined in the prototype object. Can we make the prototype property of the subclass constructor point to the prototype object of the parent constructor?

It is possible to inherit the parent method, but there is a danger that if you modify the child object, the parent object will change as well. It is therefore not recommended. So what’s the right thing to do?

We already know that the methods of prototype objects are shared by all instance objects. Therefore, the instance object created by the superclass constructor must have all the methods on its object prototype. This instance object can therefore be used as a prototype object for the subclass constructor, and a prototype chain is created. Method inheritance is also implemented.

At the same time, because the subclass prototype object is equivalent to the parent class instantiation, and the parent class instantiation after the space, so the original parent class prototype object will not be affected. Note here that since we modified the prototype object of the subclass constructor, we use constructor to refer back to the original subclass constructor.

Here is the implementation

Son.prototype = new Father(); Son.prototype.constructor = Son; Son.prototype.say = function() {console.log(' I am a child '); } const p1 = new Son(' li Si ',10) p1.run() // I can run p1.say() // I am a childCopy the code

At this point, inheritance is implemented for all members of the parent class. Thus, the inherited implementation essentially adds an endpoint to the prototype chain. In fact, this is the most common method of JavaScript inheritance, called composite inheritance.

However, by observing the implementation of composite inheritance, you can see that composite inheritance calls the constructor twice in any case: once inside the subclass constructor and once when the subclass prototype is created. Instead of calling the parent class’s constructor once to specify the subclass’s prototype, we can use the object.create method to create a copy of the parent class’s prototype as the subclass’s prototype.

let protoType = Object.create(Father);
Son.protoType = protoType;
protoType.constructor = Son;
Copy the code

This is called parasitic combinatorial inheritance, which calls the parent constructor only once and performs the same operations as combinatorial inheritance. In fact, inheritance (extends) of ES6 classes is implemented based on parasitic composite inheritance.