Prototype and prototype chain

The basic concept

To learn more about prototypes and prototype chains, here are a few things to understand:

  1. All reference types (Object, Array, Function, Date, RegExp) are objects. Objects have a __proto__ attribute.

  2. All constructors have the Prototype attribute.

  3. prototypeThink of it as a set of templates that hold methods.

    Among them:constructorIs a constructor (the function that creates the current object), that is, to see what constructors you have defined.

  4. The __proto__ attribute traces back to the prototype of the constructor that constructed it. (That is, you can use it to find out what the template of the constructor is.)

  5. Prototype is itself an object, so it also has a __proto__ attribute. (As mentioned in point 1, all objects have a __proto__ attribute.)

  6. Object is a primitive Object, so object.prototype. __proto__ points to null to avoid an infinite loop. Null and undefined have no __proto__ attribute, i.e., the highest level.

  7. The relation between Object and Function is a bit like the relation between chicken and egg, so

    Object.__proto__=== Function.__proto__ === Function.prototype
    Function.prototype.__proto__ === Object.prototype
    Copy the code

An example

With these basic concepts in mind, let’s take a look at this example:

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

const Lee = new Person('Lee')

Lee.__proto__ === Person.prototype // true
Lee's __proto__ refers to the prototype of the constructor that constructed it
Person.__proto__ === Function.prototype // true
Person's __proto__ points to the prototype of the Function that constructed it

Person.toString ƒ toString() {[native code]}
// There is no toString method in Person, but it can be referenced
// Because it inherits a set of templates from Object ↓↓↓
Person.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null // Get to the top!

As mentioned in point 3, prototype can be understood as a set of templates that hold methods. Where constructor is the constructor (the function that creates the current object), which is used to see what constructors you have defined.
// So: ↓↓↓
Lee.constructor === Person // Lee's constructor is Person
Person.constructor === Object

// The relationship between Object and Function is a bit like the chicken-egg relationship
Function.prototype.__proto__ === Object.prototype // true
Object.__proto__ === Function.prototype
Copy the code

In the form of pictures, it would look like this:

inheritance

Prototype chain inheritance

function Parent() {
	this.names = ['Debra'.'Patamo']}function Child() {}
Child.prototype = new Parent()
const child1 = new Child()
child1.names // ['Debra', 'Patamo']
Copy the code

Disadvantages:

Properties of reference types are shared by all instances.

child1.names.push('Pikachu')
child1.names // ['Debra', 'Patamo', 'Pikachu']
const child2 = new Child()
child2.names // ['Debra', 'Patamo', 'Pikachu']
Copy the code

When creating an instance of Child, you cannot pass an argument to the Parent.

Borrowing constructors (classical inheritance)

function Parent() {
	this.names = ['Debra'.'Patamo']}function Child() {
	Parent.call(this)}const child1 = new Child();
child1.names.push('Pikachu');
console.log(child1.names); // ['Debra', 'Patamo', 'Pikachu']

const child2 = new Child();
console.log(child2.names); // ['Debra', 'Patamo']
Copy the code

Advantages:

  1. Properties of reference types are prevented from being shared by all instances
  2. You can pass an argument to a Parent in a Child

Disadvantages:

  1. Only instance properties and methods of the parent class can be inherited, not stereotype properties/methods
  2. Unable to reuse, each subclass has a copy of the parent class instance function, affecting performance

Combination of inheritance

function Parent() {
	this.name = 'Debra'
}
Parent.prototype.getName = function() { return this.name }
function Child() {
	Parent.call(this) // Inherit attributes
}
Child.prototype = new Parent() // Inheritance method

const child = new Child()
child.getName() // Debra
Copy the code

Advantages:

  1. It makes up for the shortcomings of prototype chains and structural constructors and is the most used inheritance pattern in JavaScript.
  2. The ability of the instanceof operator and isPrototypeOf() method to recognize synthesized objects is preserved.

Disadvantages:

The superclass constructor was called twice, affecting performance.

Once is when we set the prototype of a subtype instance:

Child.prototype = new Parent();

When creating an instance of a subtype:

var child1 = new Child();

Recall the mock implementation of new. In fact, in this sentence, we would execute:

Parent.call(this, name);

Here, we call the Parent constructor again.

Prototype inheritance

function createObj(o) {
    function F() {}
    F.prototype = o;
    return new F();
}
Copy the code

Create is a mock implementation of ES5 Object.create that uses the passed Object as a prototype for the created Object.

Disadvantages:

Attribute values that contain reference types always share corresponding values, as with stereotype chain inheritance.

var person = {
    name: 'kevin'.friends: ['daisy'.'kelly']}var person1 = createObj(person);
var person2 = createObj(person);

person1.name = 'person1';
console.log(person2.name); // kevin

person1.friends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"]
Copy the code

Note: Person1. name = ‘person1’; person2.name = ‘person1’; person1.name = ‘person1’; You did not change the name value on the stereotype.

Parasitic inheritance

Create a function that encapsulates only the inheritance process, internally does the enhancement object in some form, and finally returns the object.

function createObj (o) {
    var clone = Object.create(o); / / the key
    clone.sayName = function () {
        console.log('hi');
    }
    return clone;
}
Copy the code

Disadvantages: As with the borrowed constructor pattern, methods are created every time an object is created.

Parasitic combinatorial inheritance (best)

function object(o) {
	let F = function () {}
	F.prototype = o
	return new F()
}

function prototype(child, parent) {
	let prototype = object(parent.prototype)
	prototype.constructor = child
	child.prototype = prototype
}
Copy the code

Advantages:

  1. The Parent constructor is called only once and thus avoids creating unnecessary, redundant properties on Parent. Prototype.
  2. The prototype chain stays the same; Therefore, instanceof and isPrototypeOf can also be used normally.