I interviewed a lot of students, few of whom could explain prototype inheritance clearly, and even fewer of whom could explain the new operator clearly. Hopefully this article will solve your doubts and help you fly through the interview process. JS prototype chain and inheritance
From JavaScript advanced programming:
Inheritance is one of the most talked about concepts in OO languages. Many OO languages support two types of inheritance: interface inheritance and implementation inheritance. Interface inheritance inherits only method signatures, whereas implementation inheritance inherits the actual methods. Interface inheritance cannot be implemented in ECMAScript because methods in JS do not have signatures. ECMAScript only supports implementation inheritance, and its implementation inheritance relies primarily on prototype chains.
concept
A quick review of the relationship between constructors, stereotypes, and instances:
Each constructor has a prototype object that contains a pointer to the constructor, and each instance contains an internal pointer to the prototype object.
There is a rule of the game in the JS object circle:
If you try to reference an attribute of an object (instance), you will first look inside the object until it is not found, and then look for the attribute in the object’s prototype (instance.prototype).
If you make the prototype object point to an instance of another type….. Interesting things happen.
Constructor1. Prototype = instance2
In view of the above rules of the game in effect, if an attempt is made to reference an instance of constructor1 construction instance1 of an attribute P1:
1). First, the instance1 internal properties will be searched;
2). Then look in instance1.__proto__(constructor1. Prototype), and constructor1. Prototype is actually instance2, so look in instance2 for p1;
3). If instance2 still isn’t there, the program won’t get discouraged, it will continue looking in instance2.__proto__(constructor2.prototype)… Prototype Object up to Object
Search path: Instance1 –> Instance2 –> Constructor2. Prototype… –>Object.prototype
The track of this search looks like a long chain, and since prototype acts as a link in the rules of the game, we call this chain between instances and prototypes prototype chain. Here’s an example
function Father(){
this.property = true;
}
Father.prototype.getFatherValue = function(){
return this.property;
}
function Son(){
this.sonProperty = false;
}
/ / Father inheritance
Son.prototype = new Father();/ / Son. The prototype be overwritten, which leads to the Son. The prototype. The constructor has also been rewritten
Son.prototype.getSonVaule = function(){
return this.sonProperty;
}
var instance = new Son();
alert(instance.getFatherValue());//true
Copy the code
The instance finds the getFatherValue method in the Father prototype through the prototype chain.
Constructor (Father) {constructor (Son. Prototype) {constructor (Father);}
We’ve figured out what a prototype chain is. If you’re not sure, please feel free to leave a comment below
Determine the relationship between the prototype and the instance
Using the prototype chain, how can we determine the inheritance relationship between the prototype and the instance? There are two ways to do this.
The first is to use the instanceof operator, which returns true whenever you test instances and constructors that have appeared in the prototype chain. The following lines of code illustrate this point.
alert(instance instanceof Object);//true
alert(instance instanceof Father);//true
alert(instance instanceof Son);//true
Copy the code
Because of the prototype chain, we can say that instance is an instance of any of the types Object, Father, or Son. Therefore, all three constructors return true.
The second is to use the isPrototypeOf() method, which again returns true for any prototype that appears in the prototype chain, as shown below.
alert(Object.prototype.isPrototypeOf(instance));//true
alert(Father.prototype.isPrototypeOf(instance));//true
alert(Son.prototype.isPrototypeOf(instance));//true
Copy the code
Same principle as above.
The prototype chain problem
The prototype chain is not perfect, and it has two problems.
Problem 1: When the stereotype chain contains a stereotype that refers to a value of the reference type, the value of the reference type is shared by all instances;
Problem two: When creating a subtype (such as an instance of Son), you cannot pass arguments to the constructor of a supertype (such as Father).
For this reason, prototype chains alone are rarely used in practice.
To that end, here are some attempts to make up for the lack of a prototype chain.
Borrowing constructor
To solve these two problems in prototype chains, we have started using a technique called constructor stealing (also known as classical inheritance).
Basic idea: Call the supertype constructor inside the subtype constructor.
function Father(){
this.colors = ["red"."blue"."green"];
}
function Son(){
Father.call(this);// Inherits Father and passes arguments to the parent type
}
var instance1 = new Son();
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
var instance2 = new Son();
console.log(instance2.colors);//"red,blue,green" can be seen that the reference type values are independent
Copy the code
It is clear that borrowing constructors solves two major problems with prototype chains at a stroke:
First, it ensures that the referenced type value in the prototype chain is independent and no longer shared by all instances.
Second, when a child type is created, it can also pass arguments to the parent type.
As a result, the problem with the constructor pattern cannot be avoided if only constructors are borrowed — methods are defined in constructors, so function reuse is not possible. And methods defined in a supertype (e.g., Father) are also invisible to subtypes. With this in mind, the technique of borrowing constructors is rarely used in isolation.
Combination of inheritance
Combinatorial inheritance, sometimes called pseudo-classical inheritance, refers to an inheritance pattern that combines a chain of archetypes and techniques borrowed from constructors to exploit the best of both.
Basic idea: use the prototype chain to achieve the inheritance of prototype attributes and methods, by borrowing the constructor to achieve the inheritance of instance attributes.
In this way, function reuse is achieved by defining methods on prototypes, while ensuring that each instance has its own attributes. As shown below.
function Father(name){
this.name = name;
this.colors = ["red"."blue"."green"];
}
Father.prototype.sayName = function(){
alert(this.name);
};
function Son(name,age){
Father.call(this,name);// Inherit the instance attribute, first call Father()
this.age = age;
}
Son.prototype = new Father();// Inherit the parent method and call Father() the second time
Son.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new Son("louis".5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();/ / 5
var instance1 = new Son("zhai".10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();/ / 10
Copy the code
Composite inheritance avoids the defects of stereotype chains and borrowed constructors, and combines their advantages to become the most common inheritance pattern in JavaScript. Also, instanceof and isPrototypeOf() can be used to identify objects created based on composite inheritance.
We also noticed that composite inheritance actually calls the parent constructor twice, causing unnecessary consumption. How can we avoid this unnecessary consumption, which we will talk about later?
Prototype inheritance
This method was first proposed by Douglas Crockford in 2006 in an article entitled Prototypal Inheritance in JavaScript. The idea is that prototypes allow you to create new objects based on existing objects without having to create custom types. The general idea is as follows:
Inside the object() function, a temporary constructor is created, the passed object is used as a prototype of the constructor, and a new instance of the temporary type is returned.
function object(o){
function F(){}
F.prototype = o;
return new F();
}
Copy the code
Essentially, object() returns a new object that refers to the object passed in. This may cause some problems with sharing data, as follows.
var person = {
friends : ["Van"."Louis"."Nick"]};var anotherPerson = object(person);
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.friends.push("Style");
alert(person.friends);//"Van,Louis,Nick,Rob,Style"
Copy the code
In this case, the person object is the basis for another object, so we pass it into the object() function, which returns a new object. This new object uses Person as a stereotype, so its stereotype contains a reference type value attribute. This means that Person. friends is not only owned by person, but also shared by anotherPerson and yetAnotherPerson.
In ECMAScript5, the original type inheritance above is normalized by adding the object.create() method.
Object.create () takes two arguments:
- An object used as a prototype for a new object
- (Optional) an object that defines additional properties for the new object
var person = {
friends : ["Van"."Louis"."Nick"]};var anotherPerson = Object.create(person);
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.friends.push("Style");
alert(person.friends);//"Van,Louis,Nick,Rob,Style"
Copy the code
Object.create () has the same function as the object method above with a single argument, and its second argument has the same format as the second argument to object.defineProperties () : each property is defined by its own descriptor. Any property specified in this way overrides the property of the same name on the prototype object. Such as:
var person = {
name : "Van"
};
var anotherPerson = Object.create(person, {
name : {
value : "Louis"}}); alert(anotherPerson.name);//"Louis"
Copy the code
Object. Create () is currently supported by Internet Explorer 9+, Firefox 4+, Safari 5+, Opera 12+, and Chrome.
Note: In primitive inheritance, attributes that contain reference type values will always share corresponding values, just as with the stereotype pattern.
Parasitic inheritance
Parasitic inheritance, also promoted by Crockford, is an idea closely related to prototype inheritance.
The idea of parasitic inheritance is similar to the (parasitic) constructor and factory pattern, in that you create a function that just encapsulates the inheritance process, enhances the object internally in some way, and finally returns the object as if it really did all the work. As follows.
function createAnother(original){
var clone = object(original);Create a new object by calling the object function
clone.sayHi = function(){// Enhance the object in some way
alert("hi");
};
return clone;// Return this object
}
Copy the code
The code in this example returns a new object based on Person –anotherPerson. The new object not only has all of the attributes and methods of Person, but it’s also enhanced to have the sayH() method.
Note: Using parasitic inheritance to add functions to an object is inefficient because it does not reuse functions; This is similar to the constructor pattern.
Parasitic combinatorial inheritance
As mentioned earlier, composite inheritance is the most common inheritance pattern in JavaScript; However, it has its own disadvantages. The biggest problem with composite inheritance is that in any case, the superclass constructor is called twice: once when the subtype stereotype is created and once inside the subtype constructor. Parasitic combinatorial inheritance appears to reduce the overhead of calling superclass constructors.
The basic idea behind this is that you do not need to call the constructor of a supertype to specify the stereotype of a subtype
function extend(subClass,superClass){
var prototype = object(superClass.prototype);// Create an object
prototype.constructor = subClass;// Enhance objects
subClass.prototype = prototype;// Specify the object
}
Copy the code
Extend’s efficiency is that it does not call the superClass constructor, thus avoiding creating unnecessary, redundant properties on subclass.prototype. At the same time, the prototype chain stays the same; Therefore, the Instanceof and isPrototypeOf() methods can also be used normally.
Above, parasitic combinatorial inheritance, which combines the advantages of both parasitic inheritance and combinatorial inheritance, is the most effective method to implement type-based inheritance.
Let’s look at another, more effective extension of extend.
function extend(subClass, superClass) {
var F = function() {};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
subClass.superclass = superClass.prototype;
if(superClass.prototype.constructor == Object.prototype.constructor) { superClass.prototype.constructor = superClass; }}Copy the code
“New F()”. Extend aims to point the prototype of the subtype to the prototype of the supertype. Why not just do the following?
subClass.prototype = superClass.prototype;// Point directly to the supertype prototype
Copy the code
Obviously, based on the above actions, the subtype stereotype will be shared with the supertype stereotype and there is no inheritance relationship at all.
New operator
And to get back to where I came from, I looked at what does the new operator do? I found it very simple, and I did three things.
var obj = {};
obj.__proto__ = F.prototype;
F.call(obj);
Copy the code
In the first line, we create an empty object obj;
In line 2, we point the empty object’s __proto__ member to the prototype member of the F function object;
In line 3, we replace the this pointer to the F function object with obj, and then call the F function.
When a constructor is called with the new operator, the following actually happens inside the function:
1. Create an empty object that is referenced by this and inherits the prototype of the function.
Properties and methods are added to the object referenced by this.
3. The newly created object is referenced by this and returns this implicitly.
The __proto__ attribute is key to specifying stereotypes
Above, the parent class is inherited by setting the __proto__ attribute. If the new operation is removed, refer directly to the following
subClass.prototype = superClass.prototype;// Point directly to the supertype prototype
Copy the code
Then, using the instanceof method to determine whether an object is an instanceof a constructor can be confusing.
If this were the case, the extend code would be
function extend(subClass, superClass) {
subClass.prototype = superClass.prototype;
subClass.superclass = superClass.prototype;
if(superClass.prototype.constructor == Object.prototype.constructor) { superClass.prototype.constructor = superClass; }}Copy the code
In this case, see the following test:
function a(){}
function b(){}
extend(b,a);
var c = new a(){};
console.log(c instanceof a);//true
console.log(c instanceof b);//true
Copy the code
It is understandable and right that C should be considered an instance of A; But c is also considered to be an instance of B, which is not true. The instanceof operator compares c.__proto__ with the constructor. Prototype (b.prototype or A.prototype). Extends (b,a); Then, b. protoType === a.prototype, so it prints the above unreasonable output.
So finally, prototype chain inheritance can be implemented like this, for example:
function Father(name){
this.name = name;
this.colors = ["red"."blue"."green"];
}
Father.prototype.sayName = function(){
alert(this.name);
};
function Son(name,age){
Father.call(this,name);// Inherit the instance attribute, first call Father()
this.age = age;
}
extend(Son,Father)// Inherit the parent method, Father() is not called a second time
Son.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new Son("louis".5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();/ / 5
var instance1 = new Son("zhai".10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();/ / 10
Copy the code
Extension:
Attributes to find
After using the prototype chain, when looking for an Object’s property, JavaScript will traverse the prototype chain up until it finds the property with the given name, until it reaches the top of the prototype chain — Object. Prototype — but still doesn’t find the specified property. Return undefined. The hasOwnProperty method is recommended to avoid prototype chain lookup. Because hasOwnProperty is the only function in JavaScript that handles properties without looking up the stereotype chain. Such as:
console.log(instance1.hasOwnProperty('age'));//true
Copy the code
IsPrototypeOf returns true if the object is a parameter, false otherwise. Such as:
console.log(Father.prototype.isPrototypeOf(instance1));//true
Copy the code
instanceof && typeof
The instanceof operator is mentioned several times above. So how does it work? Let’s take a look at how it can be used.
The instanceof operator is used to indicate at run time whether an object is an instanceof a constructor. For example, if the new operator is omitted to call a constructor, instanceof can be used within the constructor.
function f(){
if(this instanceof arguments.callee)
console.log('Called here as a constructor');
else
console.log('Called here as a normal function');
}
f();// this is called as a normal function
new f();// called here as a constructor
Copy the code
Above, the value of this instanceof arguments.callee, if true, means that it was called as a constructor, and false means that it was called as a normal function.
Contrast: typeof is used to retrieve the typeof a variable or expression, and generally returns only the following results:
Number, Boolean, string, the function (function), object (), NULL, array object, undefined.
New operator
Allen’s JS object mechanism deep dissection — new operator is referenced here
Following the above investigation of the new operator, let’s examine the definition of the new operator in the ECMAScript language specification:
The new Operator
The production NewExpression : new NewExpression is evaluated as follows:Evaluate NewExpression.Call GetValue(Result(1)).If Type(Result(2)) is not Object, throw a TypeError exception.If Result(2) does not implement the internal [[Construc]] method, throw a TypeError exception.Call the [[Construct]] method on Result(2), providing no arguments (that is, an empty list of arguments).Return Result(5).
New must be followed by an object with an inner method named [[Construct]], otherwise an exception will be thrown
Given this, we can Construct a pseudo-[[Construct]] method to simulate this process
function MyObject(age) {
this.age = age;
}
MyObject.construct = function() {
var o = {}, Constructor = MyObject;
o.__proto__ = Constructor.prototype;
[[Prototype]]
Constructor.apply(o, arguments);
return o;
};
var obj1 = new MyObject(10);
var obj2 = MyObject.construct(10);
alert(obj2 instanceof MyObject);// true
Copy the code
Unconsciously, this article has been written for 3 days, but there are still many extended things to be said. If you have any questions or good ideas, please leave comments and comments below.
Author: Louis
This paper links: louiszhai. Making. IO / 2015/12/15 /…
Reference:
- JavaScript Advanced Programming
- JavaScript instantiation and inheritance: Please stop using the new keyword
- In-depth Understanding of JavaScript Series 5: Powerful Prototypes and Prototype Chains – Uncle Tom – Blog Garden