Deep understanding of JavaScript – prototype

This article will try to answer these questions:

  • what is a prototype
  • Why have a prototype
  • What is the difference between prototype and __proto__
  • What is the prototype chain
  • How Prototypes Implement Inheritance
  • How are prototypes and prototype chains related

Overview

First, JavaScript is a language based on prototypal inheritance. A prototype (prototype) is an object that provides shared properties to other objects. Each function has a prototype property, which points to a prototype object. Every object has an implicit reference ([[Prototype]]), and [[Prototype]] points to its prototype object, from which it inherits data, structure, and behavior. At the same time, the prototype object also has a prototype (function is also an object, it also has [[Prototype]]), such a layer by layer, eventually points to null, this relationship is called a prototype chain

Essentially, prototypes are a means to achieve inheritance. Now that JavaScript has chosen to implement it this way, it is necessary for us to discuss what is prototypal inheritance? What are its pros and cons and how it differs from class inheritance and other ways of inheritance in JavaScript

At the beginning of the article, we first unify some conceptual issues for the sake of understanding later

unifying concept

Advanced JavaScript Programming 4th Edition introduces prototypes:

Whenever a function is created, a prototype property (pointing to the prototype object) is created for the function according to certain rules. By default, all prototype objects automatically get a property named constructor that points back to the constructor associated with them

The English version of Advanced JavaScript Programming 4th Edition introduces prototypes:

Whenever a function is created, its prototype property is also created according to a specific set of rules. By default, all prototypes automatically get a property called constructor that points back to the function on which it is a property.

Prototypes are defined in the ECMA specification as follows:

4.4.8 prototype

object that provides shared properties for other objects

It is defined as: an object that provides shared properties for other objects

MDN introduces the prototype:

Following the ECMAScript standard, the someObject.[[Prototype]] symbol is a prototype used to point to someObject . Starting with ECMAScript 6, [[Prototype]] can be accessed via the Object.getPrototypeOf() and Object.setPrototypeOf() accessors. This is equivalent to JavaScript’s non-standard but many browser-implemented property __proto__

But it should not be confused with the prototype property of the constructor func . The [[Prototype]] of the instance object created by the constructor points to the func prototype attribute of —9e157b574a186f46f3a7bdcc7acce0bc—. Object.prototype property represents the prototype object of Object

So we understand the prototype like this:

  • Also known as prototype, its responsibility is to provide shared properties to other objects
  • From the point of view of data structure, it is a singly linked list
  • Prototype object: each function has a prototype property, which is a pointer to an object, which is called a prototype object; each object has a [[Prototype]] property, which is also a pointer, pointing to the prototype object
  • Prototype property: Each function has a prototype property, called the prototype property, pointing to the prototype object
  • Therefore, prototype, prototype, prototype object, and prototype property are actually different names for the same thing, just like a person is a child in the eyes of parents, a parent in front of children, and a passerby on the road. When we call this thing a prototype, what we want to express is what it does; when we call it a prototype object, it is because each object has its own [[Prototype]] attribute when it is created, and points to it; When we call it a prototype property, it is because each function will have its own prototype property when it is created, and this property is a pointer to the prototype object

In addition, there are some concepts:

  • Function objects: All instances of Function (built-in constructors) are function objects
  • Ordinary objects: All objects except function objects are ordinary objects
  • Constructor: Also known as the constructor, the English name is constructor
  • Implicit prototype: __proto__ , aka [[Prototype]], which points to the prototype object

In this way, we unify the concepts, and then explain what is a prototype, why there is a prototype… and so on

Glossary

prototype

The prototype reminds me of “One Hundred Years of Solitude”, a family story of a family of six generations. I still remember a quote from the book:
The first person in the family is tied to a tree, the last person in the family is being eaten by ants

Why does the author think of “One Hundred Years of Solitude” when I see the prototype? Because the author is often confused by terms and concepts such as prototype, prototype object, prototype, __proto__ , [[Prototype]], etc., just like the character names in “One Hundred Years of Solitude”, I can’t tell who is who after a while who is it

Whether it’s the book or the spec, there’s an explanation for the archetype:

Every function in JavaScript has a prototype property, which points to the prototype object; every object has a [[Prototype]] property, which points to the prototype object

Give you an example

 function Foo() {}
Foo.prototype.name = 'johan';
console.dir(Foo);
console.dir(Foo.prototype);

After printing:

Printing the prototype of Foo, we see three properties. And we only assign name, why are there two more parameters?

In fact, the bottom layer of the language helps us realize that no matter what object, as long as it is created, it will have its own constructor and [[Prototype]]. The prototype object is also an object, that is, Foo.prototype is an object, so it also has constructor and [[Prototype]]

Of course, since a function is also an object, it also has a constructor and [[Prototype]]

It needs to be explained here: although printing Foo in the browser does not see the constructor attribute, it does exist, and it points to the Function built-in constructor

Here we can confirm that as long as a function is created, the function will have its own prototype property, which is an object with [[Prototype]] and constructor. So what is [[Prototype]]

[[Prototype]] and __proto__

In the previous example, Foo.__proto__ is used to print the log, instead of Foo.[[Prototype]] , and when printing Foo, the name of the implicit prototype is [[Prototype]], but in other You can see in the article __proto__

In fact, both [[Prototype]] and __proto__ refer to the same thing. In the earlier article, to distinguish the prototype, we called it implicit prototype. And its emergence is a historical issue

The official ECMAScript stipulates that prototype is an implicit reference, but civil browsers have opened up and implemented a property __proto__ , so that instance objects can access prototype objects through __proto__ . Later, the official had to bow to the facts and incorporate the __proto__ attribute into the specification. Later in ECMAScript 2015, the getPrototypeOf() and setPrototypeOf() methods were proposed to get/set prototype objects

As for [[Prototype]], it is only displayed when the browser prints. It has the same meaning as __proto__ , but the browser manufacturer has changed the vest. And the [[Prototype]] (or __proto__ ) that we can see in the developer tools is a virtual node that is intentionally rendered by the browser manufacturer. The object does not actually exist

So the [[Prototype]] attribute can neither be traversed by for in , nor can it be found by Object.key(obj)

In the previous article, we explained that each object has the [[Prototype]] attribute, which points to the prototype object, and the prototype object also has its own implicit reference ([[Prototype]]) and its own prototype object, which we can understand as parent class object. Its function is that when you access an attribute, if the attribute does not exist in the object, it will follow the [[Prototype]] attribute to point to its prototype object (parent object) to search, if the parent object still does not have this value, it will look up its parent object along the [[Prototype]] of the parent object. And so on until null is found. This layer-by-layer retrospective search process constitutes the prototype chain

prototype chain

The prototype chain is the product formed by the combination of prototype and [[Prototype]]

Give you an example

 function Person(name) {
  this.name = name;
}
Person.prototype.sayName = function () {
  return this.name;
};
var johan = new Person('johan');
console.log(johan.sayName()); // 'johan'
console.log(johan.toString()); // '[object Object]'

This involves inheritance and new keywords, which will be explained later. It is assumed that you already understand the basic concepts.

We created a constructor Person, and created a method sayName on its prototype, the new Person instance object johan, the only value on the johan attribute at this time is name

When we use the method johan.sayName(), it looks for its own properties and can’t find the sayName method, so it looks for its prototype object along [[Prototype]], namely Person.prototype, where it finds sayName, call it to return the value

When we call the method johan.toString(), likewise, we look for our own properties, and we can’t find it along [[Prototype]] to find its prototype object. As above, if we still can’t find it, we go along the lines of Person.prototype. Look up the prototype object, ie Person.prototype.__proto__ , here, find the attribute toString, call and return the value

If you are familiar with the properties above, you can understand that it is Object.prototype, which means

 Person.prototype.__proto__ === Object.prototype; // true

That is, the prototype of the constructor Person inherits from Object.prototype

This is the role of the prototype chain, so JavaScript is a language based on prototypal inheritance

So relying on the prototype, you can achieve inheritance, as for the constructor (constructor), it can also achieve inheritance, but it is another topic

constructor

The constructor function has been used in the previous article. Its function is to initialize the object and assign initial values to the object member variables.

Regarding the constructor, we can derive the chicken-and-egg problem of the constructor’s ancestor Function (built-in constructor) and the prototype’s ancestor Object.prototype. For details, please refer to this article – The First Emperor in JavaScript (subsequent article update)

Inheritance can also be achieved by stealing constructors, and more possible inheritance can be achieved by combining with prototypes. The author will write all the specific methods in this article – inheritance (subsequent article update)

But before understanding inheritance, let’s look at prototypal inheritance

Object creation and prototypal inheritance

When talking about Object , I mentioned that there are three ways to create objects, object literal, keyword new, Object.create, and all three are prototypal inheritance

The so-called prototypal inheritance is nothing more than setting one object as the prototype of another object

In JavaScript, there are two types of methods of prototypal inheritance: explicit inheritance and implicit inheritance. The difference between the two is whether or not to actively operate. Object literals and the keyword new are implicit inheritance. The bottom layer of the language does inheritance for us (as in the first example above). Object.create is explicit inheritance, which requires manual operation by developers. In addition, there is an explicit inheritance – Object.setPrototypeOf

Object.setPrototypeOf

This method sets the prototype of a particular object to another object or null. ES6 new method. The syntax is: Object.setPrototypeOf(obj, prototype) . Specific examples are:

 const obja = { a: 1 };
const objb = { b: 1 };

Object.setPrototypeOf(obja, objb);
console.log(obja);
Object.setPrototypeOf

It can be seen that we set objb as the prototype of obja through the Object.setPrototypeOf method. When printing obja, we can see that the implicit prototype of obja ([[Prototype]]) points to objb

In addition, there is a method to explicitly inherit the prototype – Object.create

Object.create

It is used to create a new object, using an existing object as the prototype of the newly created object. ES5 new method. Its syntax is Object.create(proto, propertiesObject) . Available cases:

 const obja = { a: 1 };
const a = Object.create(obja);
console.log(a);
Object.create

The author has written an article before this – Object.create , to introduce how it is implemented, and the bottom layer is to use prototypal inheritance

We compare Object.setPrototypeOf and Object.create

  • Object.setPrototypeOf, gives me two objects, set one as the prototype of the other
  • Object.create, gives me an object to use as the prototype for new objects I create

From a development point of view, Object.create appeared in ES5, but when it does not satisfy the setting prototype of two objects, ES6 provides a new method – Object.setPrototypeOf. But in any case, they are all APIs added later due to individual needs, and cannot be compared with the implicit inheritance adopted by JavaScript at the beginning

Implicit prototypal inheritance

Here, we may wish to review the new implementation principle:

  1. create a new object
  2. Set the object’s [[Prototype]] to the constructor prototype property
  3. Assign this inside the constructor to the object
  4. Execute the inner code of the constructor
  5. If the constructor returns a non-null object, return that object; otherwise, return the new object just created

This is implicit inheritance. As long as we use new to create an object, the above steps will be performed. Of course, using object literals has the same implicit operation.

 var obj = {}; // === new Object() 对象字面量
var arr = []; // === new Array() 数组字面量
function func() {} //  new Function(); 函数字面量
var str = '123'; // === new Srting('123') 字符串字面量
var bool = true; // === new Boolean(true) 布尔字面量
// ...

JavaScript provides several built-in constructors, such as Object, Array, Boolean, String, Number, etc. When we use {} , [] , function , etc. symbol or keyword, you are performing the same implicit prototypal inheritance as the new operation

We can say this: the purpose of implicit prototypal inheritance is to facilitate developers to implement inheritance more concisely

Convert between implicit prototypal inheritance and explicit prototypal inheritance

Whether it is implicit prototypal inheritance or explicit prototypal inheritance, it is the application of implicit references to objects. There is some interoperability between the two, that is, with one, part of the behavior of the other can be achieved

For example, we implement Object.create with implicit prototypal inheritance, and the implementation is a handwritten Object.create method, as we have implemented in Object.create :

 function create(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

The principle is also very simple, create a function, assign its prototype to the target object, instantiate the function, and return the instantiated value. new instantiation, equivalent to [[Prototype]] of the instantiated value points to the target object

 function create(proto) {
  function F() {}
  // 创建一个函数,每个函数都有一个 prototype 属性,指向原型对象
  F.prototype = proto;
  // 原本的 prototype 是一个对象,其中有两个属性,一个是 constructor,即构造函数,指向 F;另一个为 [[Prototype]],指向 Object.prototype
  // 现在将它赋值为 proto
  return new F();
  // new的作用是创建空对象,将该对象的原型赋值为另一个构造函数的 prototype 属性,并执行该构造函数
  // 所以 new F() 后的实例的的[[Prototype]]指向 F.prototype,也就是传入的 proto
}

Above, we use new to achieve explicit prototypal inheritance

So how do you implement new (or object literals) with explicit prototypal inheritance?

We have already implemented it in handwritten new , paste the code here:

 function new2(Constructor, ...args) {
  var obj = Object.create(null); // 创建一个对象
  obj.__proto__ = Constructor.prototype; // 将新对象的 [[Prototype]] 属性赋值为构造函数的原型对象
  var result = Constructor.apply(obj.args); // this赋值新对象并初始化
  return typeof result === 'object' ? result : obj; // 非空返回结果
}

This also explains two interview questions that are often tested in interviews: handwritten new and handwritten Object.create, one of which is implicit prototypal inheritance and the other is explicit prototypal inheritance. The two can implement each other’s methods through their own characteristics.

Summarize

In this way, we understand what the prototype is, the relationship between the prototype and the prototype chain, and so on. And we know what the phrase “JavaScript is a language based on prototypal inheritance” means.

Although the prototype is now unimportant in the interview, there was such a problem on Zhihu – interviewing a 5-year-old front-end, but even the prototype chain is not clear, and the mouth is full of Vue, React and other implementations, so Should people use it? . It is normal to develop a business without understanding prototypes. The author thinks this is not surprising, after all, the front-end development of the front-end has shifted from object-oriented to functional programming

Answer the questions at the beginning of the article at the end of the article

Q: What is the prototype?

A: An object that provides shared properties to other objects

Q: Why have prototypes?

A: JavaScript is a language based on prototypal inheritance, here is to achieve inheritance

Q: What is the difference between prototype and __proto__

A: Prototype is a function-specific property. Each function has its own prototype property when it is created. It points to an object, which is called a prototype object. And each object has a __proto__ attribute, which also points to the prototype object. What if the two are related? Then the child object __proto__ === the prototype of the parent object

Q: What is the prototype chain?

A: Each object has the __proto__ attribute, which points to the prototype object, the prototype object is also an object, and also has the __proto__ attribute, and points to its prototype object. points to null, this relationship is called the prototype chain.

Q: How does the prototype implement inheritance

A: There are four methods of prototypal inheritance, which are divided into implicit prototypal inheritance and explicit prototypal inheritance according to whether manual operation is performed. Implicit prototypal inheritance accounts for the majority of our development, namely object literals and new, that is, the bottom layer of these two method languages will help us create objects, associate prototypes and initialize properties. Explicit prototype inheritance is divided into Object.create and Object.setPrototypeOf, which can actively set an object as the prototype of another object

Q: What is the relationship between prototype and prototype chain

A: Prototype is a method to achieve inheritance, and prototype chain is the product of inheritance

References

series of articles