This article focuses on JavaScript prototype chain related knowledge. See the relationships between classes, constructors, and sample objects in graphic form. It also covers the details of prototype chain lookup, how to get and set a property, and so on. Finally, inheritance via prototype chain is shown.

Class, constructor, Prototype object, and instance object

Let’s start with some code:

function Person(name) {
    this.name = name
}
Person.prototype.printName = function () {
    console.log(this.name)
}
​
let instance1 = new Person("Moxy");
let instance1 = new Person("Ninjee");
Copy the code

1.1 the noun

Class, constructor, stereotype object, and instance object.

  1. Class: with the same name as the constructor, the class name in the code isPerson.
  2. Constructor: in codePersonFunctions are often called constructors. The function itself is not a constructor, but is preceded by a normal function callnewAfter the keyword, it turns the function call into a“Constructor call”
  3. Stereotype object: Each constructor has a stereotype object.Constructor. PrototypeIt points to the prototype object. In the code, that isPerson.pototype.
  4. Instance object: by callingnewThe instantiated object created by the + constructor is the instance object. In the codeinstance1instance2That’s the instance object.

1.2 relationship

Introduce the relationship between the four nouns above, i.e. how they are connected.

1.2.1 Point to the prototype object

In the code, the prototype object is anonymous and does not have an obvious name. So typically, prototype objects are represented by the constructor name: Person.prototype. This expression of the prototype object also illustrates the relationship between the constructor and the prototype object:

  1. Person.prototypeOf the constructor.prototypeProperty that points to the prototype object.
  2. instance1.__proto__That is, of the instance object.__proto__Property that points to the prototype object.

1.2.2 Pointing to constructors

By default, the created prototype object will have an attribute that is not enumerable. Constructor points to the constructor.

  1. Person. The prototype. The constructor, namely a prototype object. The constructor attribute points to the constructor.

  2. Instance1. constroctor, that is, the.constroctor property of the instance object points to the constructor.

  • Note: In fact, instance objects are not.constroctorProperties. Can be achieved by.constroctorThe constructor is accessed because the prototype object is accessed through the prototype chain.constroctorThe property, the real access chain, is:instance1.__proto__.constroctor. More on prototype chains later.

2 prototype chain

Objects in JavaScript have a special [[prototype]] built-in property that is essentially a reference to another object. Almost all objects * are created with their own [[prototype]] property given a value that points to another object.

  • Due to the[[prototype]]Is a built-in property that we can’t explicitly perceive, so the browser defines a nonstandard.__proto__Property, that property represents[[prototype]]. In fact,.__proto__It’s not a property, it’s the equivalent of a function call, you can think of it as__proto__()The process is more like one[[Get]], will be further explained later in “Checking class relationships”.
  • With the exception of “almost all objects.Object.create(null)Methods will be explained below.

Prototype chain: the [[prototype]] built-in property of A objects points to B objects, and the [[prototype]] built-in property of B objects points to C objects… Object. Prototype. The chain of such objects pointing to other objects is known as a prototype chain.

From the point of view of data structure, prototype chain is actually a one-way linked list, each node is an object.

2.2 Prototype chain of class

Through four pictures, describe the specific process of prototype chain.

First, explain the models involved in the image:

  1. There are three classes, built into JavaScriptObjectAnd the parent classFather, a subclassSon, there is an inheritance relationship among the three.
  2. Class prototype objects are uselessObject.prototypeThe prototype of Object.
  3. Each constructor enumerates three instance objects, named by the constructor name + a number, as in:object1.

The image above explains how the instance object walks through its prototype chain step by step through.__proto__, the main character of which is the.__proto__ property.

As you can see, all instance objects refer to their prototype objects via the.__proto__ attribute. Each prototype object then points to another prototype object through the.__proto__ property because of inheritance. Such a series, formed a complete prototype chain. Eventually, all prototype objects refer to the prototype Object of Object, that is, Object.prototype. To express that Object.prototype is the root of all prototype chains, its.__proto__ property points to NULL.

  • Not all objects end up pointing toObject.prototype. throughObject.create(null)Objects created are not inheritedObject. Of its prototype object.__proto__Will be displayedundefined.
    function Father(name) {
        this.name = name
        this.colors = ["red"."blue"."green"]
    }
    Father.prototype.__proto__ === Object.prototype // true, the prototype Object points to Object.prototype by default// Break the prototype link with object.create (null)
    Father.prototype = Object.create(null) 
    Father.prototype.__proto__ === Object.prototype // false, the prototype chain of the prototype object has been changed.
    Father.prototype.__proto__      // undefined, in fact, the prototype chain of the prototype object.__proto__ is removed.
Copy the code

Object.create()

It creates an object and associates its [[Prototype]] prototype chain with the specified object.

In fact, inheritance through a prototype chain is a linked list, which is more like an “elevator” :

All prototype chains are concatenated through __proto__ attributes. The built-in attribute [[prototype]].

The archetypal object of inheritance is the elevator at each level: Son archetypal object at level 1, Father archetypal object at level 2. The prototype Object of Object is always at the top level because it represents the root node of the prototype chain. Its.__proto__ always points to null.

The class name, or constructor, is the name of each floor; The instance objects are different rooms on each floor. The result is the following model, with a dark red arrow representing how the lowest instance object is traversed up the [[Prototype]] prototype chain.

The chart below explains:

  1. The constructor passesnewThe operator creates an instance object.
  2. constructional.prototypeThe property value points toObject.prototypeThe prototype object of the constructor.

3 New operator

The process of new a new object, what happens?

let person1 = new Person("Moxy".15);
Copy the code

To create a new instance of Person, you must use the new operator. Calling the constructor in this way actually goes through five steps:

  1. To create aA new object {};
  2. Bind for the new objectPrototype chain:{}.__proto__ = Person.prototype;
  3. Scope the constructorthisAssign to a new object{};
  4. Execute the code in the constructor, as{}Add attributes:Person.call(this);
  5. If the constructor eventually returns an object, the object in the constructor is returned.
  6. If the constructor returns no other object, the new object is returned.

Finally, the person1 variable on the left side of the code receives the newly created object.

4. Setting and masking of attributes

To change a property of an object, JavaScript needs to determine whether the property already exists. Is it an inherited property that exists on the stereotype chain?

Person. Name = “Moxy” triggers [[Put]].

First, it checks whether the property value already exists in the object, and if so, it executes 1. If no, go to 2.

  1. Judgment has its own attributes. If there is a data attribute with the same name in the Person object, the statement assigns to modify the existing attribute.

  2. Determine inheritance properties. Walk through the prototype chain of the Person object.

    1. I couldn’t find it. If no name attribute of the same name is found on the prototype chain, the statement creates a new attribute name on Person. Otherwise, perform 2.2.

    2. Find. If the name attribute of the same name can be found on the prototype chain, it can be divided into three cases:

      1. Can write. If I find a property with the same nameData attributesAnd to writewritable:false. The create action occurs in thepersonCreating a new property name;
      2. Do not write. If I find a property with the same nameData attributes, but cannot be writtenwritable:true. Then the statement will beSilently ignored, error in strict mode:TypeError;
      3. Setter. If you find a property with the same name that is a property that has setter function accessors. This statement makes a setter function call.

Conclusion:

  1. The setting of an attribute is to modify an existing attribute value. The so-called attribute shielding means that if an attribute of the same name exists on the prototype chain of the target object, the attribute of the same name on the prototype chain will be shielded if a new attribute is created on the target object.
  2. All data attributes apply to this rule, includingBasic data typesReference attribute type(methods, arrays, etc.). If a parent class exists with the same namenameArray, executeperson.name = "Moxy"In thepersonObject with the same namenameInstead of an array of reference attribute types, it is a string of primitive data types.
  3. Rule 2.2.2 “cannot write” can be considered as follows: If an inherited attribute is not allowed to write in its parent class, its inherited subclass is also not allowed to write.
  4. The case for “setter” in rule 2.2.3 can be considered as follows: if an inherited property has a setter in the parent class, the assignment of the subclass also calls that setter.

5. Original type inheritance

For more on this, see “Original type inheritance” in the “Inheritance” chapter. Only the implementation code is given here:

// Define parent class: instance attribute + public method
function Father(name) {
    this.name = name
}
Father.prototype.printName = function () {
    console.log(this.name)
}
​
// Define subclasses: instance attributes + public methods. Now common methods can be defined next to instance properties
function Son(name, age) {
    Father.call(this, name)
    this.age = age
};
Son.prototype.printAge = function () {
    console.log(this.age)
}
// Method 3: Original type inheritance. Using the ES6 method,
Object.setPrototypeOf(Son.prototype, Father.prototype)
​
// Instantiate test:
let instance1 = new Son("Moxy".99);  // Son {name: "Moxy", age: 99}
let instance2 = new Son("Ninjee".5); // Son {name: "Ninjee", age: 5}
instance1.printName === instance2.printName   // true
Son.prototype.__proto__ === Father.prototype  // true
Copy the code

Six kinds of relationship judgment

For more on type judgment: see the “Type Judgment” section of the “Type” chapter.

Method of type judgment:

  • typeofOperators. Basic data type values can be determined.
  • instenceofOperators. It is possible to determine reference type values, but not very useful.
  • Object.prototype.toString()Function. Can determine the reference type value, insteadinstanceofOperators.

In a traditional class-oriented environment, checking the ancestry of an instance (object in JavaScript) (delegate association in JavaScript) is often referred to as introspection (or reflection)

Note: entrusting relation is what we call inheritance relation.

Method 1: the instanceof operator

Instance1 instanceof Father this code answers the question:

Is there a Father. Prototype prototype in the entire prototype chain of instance1?

  • Note: in this code “Father” is the constructorFather“, and what the code is really looking for is the constructor’s prototype objectFather.prototypeIt’s easy to get confused.

IsPrototypeOf ()

Father. Protoype. IsProtoypeOf (instance1), this code is to answer the question is:

Is there a Father. Prototype prototype in the entire prototype chain of instance1?

You can see that this method acts exactly like the instanceof operator. The advantage is that the Father constructor is removed. Prototype object Father. Prototype

Object.getprototypeof ()

Object.getprototypeof (instance1), this code resolves:

Gets the prototype object of the instance object instance1.

Note: This method returns its prototype object. Unable to return the complete prototype chain. If you want to get a complete prototype chain, you can call this method repeatedly, iterating through the entire prototype chain until null terminates:

function getProto(obj) {
    if (obj === null) return;
    console.log(obj)
    return Object.getPrototypeOf(obj)
}
Copy the code

Method 4:.__proto__

Instance1.__proto__ === Son. Prototype

Is the prototype object of instance1 Son. Prototype?

This property is a browser-recognized property, not an official JS standard. In fact, the property name is more like a getter-like method. Its internal implementation relies on the object.getProtoTypeof () method and is not recommended.

// The prototype chain can be traversed in this way.
instance1.__proto__.__proto__ ....
Copy the code

Seven questions:

Thank you for compiling the titles from other authors.

1. Look at the code and recognize the result:

var A = function () {};
A.prototype.n = 1;
var a1 = new A();
A.prototype = {
    n: 2,
    m: 3
}
var a2 = new A();
​
console.log(a1.n); 
console.log(a1.m); 
​
console.log(a2.n); 
console.log(a2.m); 
Copy the code

Answer:

After instance object A1 is created, the following code is executed: a.protoType = {n: 2.m :3}.

This results in A change in the prototype object of constructor A: instead of {n:1}, it becomes A new object, {n: 2.m :3}. That is, the address value is no longer the same object, but a new object.

As we know, when the new operator instantiates an object, it also points the __proto__ attribute of that instance object to the object from which the constructor is derived. So,

  • a1.__proto__I’m pointing to an old object{n:2};
  • a2.__proto__It points to a new object{n:2, m:3}.

The two instance objects point to different objects, and the results of the prototype chain search are naturally different.

Conclusion:

Don’t easily redefine. Prototype objects. This leads to:

  1. Archetypal objectconstructorAttribute missing. It originally pointed to the constructor, which needs to be specified manually after the prototype object is redefinedconstructorProperties.
  2. Before and after a prototype object is redefined, the instantiated object points to different prototype objects, resulting in inconsistent performance.
// Do not redefine the prototype object as follows:
A.prototype = {
    n: 2.m: 3
}
// Add attributes and methods to the prototype object like this
A.prototype.n = 2;
A.prototype.m = 3;
Copy the code

The answer:

console.log(a1.n); / / 1
console.log(a1.m); // undefinedconsole.log(a2.n); / / 2
console.log(a2.m); / / 3
Copy the code

2 look at the code to recognize the result:

var F = function () {};
​
Object.prototype.a = function () {
    console.log('a');
};
​
Function.prototype.b = function () {
    console.log('b');
}
​
var f = new F();
​
f.a()
f.b()
​
F.a()
F.b()
Copy the code

Answer:

  1. FIs a constructor, of function type, and is an object;
  2. fIs an instance object that belongs toFType, is an object.

All objects are Object objects. So all objects inherit from Object, including F and F in the problem. Then a method on the Object prototype chain, f and f can be called;

The constructor F is a Function Function type. The instance object f is not Function. So the b method on the Function prototype chain, only F can be called, F cannot be called.

The answer:

f.a() // a
f.b() // f.b is not a function
​
F.a() // a
F.b() // b
Copy the code

3 look at the code answer:

function Person(name) {
    this.name = name
}
let p = new Person('Tom');
Copy the code

Question 1: What is p.__proto__ equal to?

Question 2: What is Person.__proto __ equal to?

Answer:

It is consistent with the second question:

Person is a Function class, so its prototype chain points to the prototype object of Function. Prototype;

P is the Person class, whose prototype chain points to Person’s prototype object person.prototype;

B:

p.__proto__.__proto__ === Person.__proto__.__proto__ // true

  • Function.prototype, it isObjectClass, so its prototype chain points toObject.prototype;
  • Person.prototype, it isObjectClass, so its prototype chain points toObject.prototype;

That is, the prototype Object for Person and Function refers to the same Object, the prototype Object for Object.

The answer:

__proto__ points to: Person prototype. Prototype.

Person.__proto__ refers to: Function prototype.

// Console output:
p.__proto__  / / {constructor: ƒ}
Person.__proto__  ƒ () {[native code]}
Copy the code

4 look at the code to recognize the result:

function fn1() {
    console.log(1);
    this.num = 111;
    this.sayHey = function () {
        console.log("say hey."); }}function fn2() {
    console.log(2);
    this.num = 222;
    this.sayHello = function () {
        console.log("say hello.");
    }
}
fn1.call(fn2);  // Is there any output here?
​
fn1(); 
fn1.num; 
fn1.sayHey(); 
​
fn2(); 
fn2.num; 
fn2.sayHello();
fn2.sayHey(); 
Copy the code

Answer:

  1. fn1fn2It’s an object, it’s a function.fn1As an object,fn1()Think of it as a function.
  2. fn1.call(fn2)It was executedfn1(). At the same time,fn1The inside of thethisValue, pointing tofn2Object.
  3. And that leads tonum,sayHeyBoth of these attributes are assigned to the objectfn2. sofn2As an object, it now hasnumsayHeyThese two properties.
  4. fn1No other attributes are assigned at this point, sofn1.xxxAre undefined;fn2.numfn2.sayHeyThere is.

The answer:

fn1.call(fn2);  // 1, where fn1() naturally outputs the number 1.
​
fn1();      / / 1
fn1.num;    // undefined
fn1.sayHey();   // fn1.sayHey is not a function
​
fn2();      / / 2
fn2.num;    / / 111
fn2.sayHello(); // fn2.sayHello is not a function
fn2.sayHey();  //say hey.
Copy the code

B:

Both apply() and call() exist to change the runtime context of a function.

  • In other words, to change what’s inside the functionthisPointing to.
let a1 = add.call(son, 4.2)   // The arguments are: this for add, the first argument for add, and the second argument for add
let a1 = add.apply(son, [4.2]) // The arguments are: add this, add parameter array collection
Copy the code

Since both methods are called immediately, to compensate for their absence, there is a method bind() that is not called immediately:

Instead of executing a method, bind returns a new method. The new method’s this points to the argument fixed to bind.

The original method is not affected.

let newAdd = add.bind(son)  // Return a new method, newAdd, whose this is fixed to son
Copy the code

5 look at the code answer:

Object.prototype.__proto__    // null
Function.prototype.__proto__  // Object.prototype
Object.__proto__              // Function.prototype
Copy the code
  • Object.prototypeThe root node of the prototype chain.Object.prototypeThe prototype object of isnull.
  • Function.prototypeIs a prototype object. So it isObjectClass. Prototype chain pointingObject.prototype.
  • ObjectIs a constructor. So it isFunctionInstantiation object of a function class. The prototype chain naturally pointsFunction.prototype.

6 look at the code answer:

Implement the Person and Student objects as follows

  • StudentinheritancePerson;
  • PersonContains an instance variablenameContains a methodprintName;
  • StudentContains an instance variablescoreContains a methodprintScore;
  • allPersonStudentSharing between objectsprintNameMethods;
// Use the constructor to implement:
function Person(name) {
    this.name = name
}
Person.prototype.printName = function () {
    console.log(this.name)
}
​
function Student(name, score) {
    Person.call(this, name)
    this.score = score
};
// Use the original form inheritance
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
​
Student.prototype.printScore = function () {
    console.log(this.score)
}
​
​
// Use class to implement:
class Person {
    constructor(name) {
        this.name = name
    }
    printName() {
        console.log(this.name)
    }
}
class Student extends Person {
    constructor(name, score) {
        super(name);
        this.score = score;
    }
    printScore() {
        console.log(this.score)
    }
}
​
​
// Instantiate test:
let instance1 = new Student("Moxy".99);  // Student {name: "Moxy", score: 99}
let instance2 = new Student("Ninjee".5); // Student {name: "Ninjee", score: 5}
instance1.printName === instance2.printName // true
Copy the code

Reference:

On JavaScript you Don’t Know

JavaScript Advanced Programming Edition 4

🍭 Illustrated prototype and prototype chain (juejin. Cn)