Let’s start with object orientation
Object orientation is a programming idea, which is characterized by polymorphism and inheritance. It usually involves concepts such as classes, instances of classes (objects) and interfaces. There are no classes in JavaScript, but we can use constructors and stereotypes to simulate class implementation.
How does JS implement inheritance
When it comes to JS inheritance, the first thing that comes to mind is certainly the prototype chain. In fact, JS inheritance is a mechanism that depends on the prototype chain, so let’s look at the prototype chain in detail.
What is a prototype chain
The concept of stereotype chains is described in ECMAScript as the primary method of implementing inheritance. The basic idea is to use stereotypes to make one reference type inherit the properties and methods of another. A quick review of the relationship between constructors, stereotypes, and instances: Each constructor has a stereotype object, which contains a pointer to the constructor, and each instance contains an internal pointer to the stereotype object. So what if we made the prototype object equal to an instance of another type? Obviously, the stereotype object will contain a pointer to another stereotype, which in turn will contain a pointer to another constructor. If another prototype is an instance of another type, then the above relationship still holds, and so on, the chain of instance and prototype is formed. This is the basic concept of the so-called prototype chain. — JavaScript Advanced Programming (version 3)
Here’s a summary of the prototype chain concept: Each constructor has a prototype object, which contains a pointer to the constructor, and each instance contains an internal pointer to the prototype object (__proto__). If we make the prototype object of type A equal to another instance of type B, then all instances of type A have A pointer to an instance of type B. Likewise, if we make the prototype object of type B equal to another instance of type C, then all instances of type B have A pointer to an instance of type C. This implements a structure similar to a linked list. Let’s use the prototype to observe the structure of the prototype chain:
function Dog(name){
this.name=name;
}
Dog.prototype={
age:13
}
let dog=new Dog('tom');
console.log(dog)
console.log(dog.age)
Copy the code
The output is shown below.
We found thatdog
Not on the objectage
Property, but we printdog.age
It prints 13, which is our functionDog
theprototype
Object to which the property is assignedage
And we find thatdog
A new object is added to the__proto__
Property, let’s expand it.
Prototype: {age:13}. When we access the age attribute that doesn’t exist in Dog, js internally looks for the object to which the __proto__ attribute points and returns it if it does. If it does not exist, the __proto__ attribute on the Object to which __proto__ points will be accessed until the __proto__ attribute is found on the Object. To verify the above statement, we modify the above constructor as follows:
function Dog(name){
this.name=name;
this.age=0;
}
Copy the code
The above code is given directlyDog
To the constructorage
The output of the assignment operation is as follows:
We see that the age returned is 0. So why does our newly created object have a __proto__ attribute pointing to constructive. prototype? This property is actually added to the object when we use the new keyword, so let’s take a guess at the implementation of new. We use the new keyword to do the following:
- Create an object
- Execute the constructor for this with the new object
- Adds a __proto__ attribute to an object
- Returns the created object
Based on the above steps we implement the new keyword, the code is as follows:
function New(f,... args){
// Create an object without any attributes
let obj={};
// Execute f on obj, that is, execute the constructor obj for this
f.apply(obj,args);
// Assign a value to __proto__
obj.__proto__=f.prototype;
// Returns the processed object
return obj;
}
Copy the code
The last return statement here is actually not rigorous, because there is a case where our constructor itself returns an object. Instead of returning the newly created obj, we follow the constructor’s return, but this breaks the prototype chain and should be avoided in practice if inheritance is required. The modified New function is:
function New(f,... args){
// Create an object without any attributes
let obj={};
// Execute f on obj, that is, execute the constructor obj for this
let res = f.apply(obj,args);
// Assign a value to __proto__
obj.__proto__=f.prototype;
// Returns the processed object
return typeof res === 'object'? res:obj; }Copy the code
Understanding the above knowledge, we can rely on the prototype chain mechanism to achieve inheritance, let us say in turn using the prototype chain to achieve JS inheritance in three ways:
- Prototype inheritance
- Combination of inheritance
- Parasitic combinatorial inheritance
Constructor inheritance
We start by inheriting the simplest constructor
function SupperType(){
this.data=[1.2.3];
this.add=function(num){
this.data.push(num)
}
}
funciton SubType(name){
this.name=name;
SupperType.apply(this)}Copy the code
Principle is very simple, the constructor is called directly in the subclass constructor of the parent class constructor, but its existing problems are also very clear that function cannot reuse problems is, every time we create an object that is to open up new a block of memory to store an add function, but this function can actually use the prototype to save reuse, Let’s solve this problem with prototype inheritance.
Primary inheritance
Stereotype inheritance is the use of the attribute lookup mechanism on the stereotype chain to share attributes and methods, where we hang the methods to be shared on the stereotype and write the private variables in the constructor.
function SupperType(){
this.data=[1.2.3];
}
SupperType.prototype.add=function(num){
this.data.push(num);
}
sub=Object.create(new SupperType());// Inherits the parent class
sub.name='name';// Subclass attributes
sub.clear=function(){// Subclass methods
this.data=[];
}
console.log(sub.data);
Copy the code
Here’s what object.create actually does:
Object.create=function(proto){
function F(){};
F.prototype=proto;
return new F();
}
Copy the code
The original form is still not perfect. We managed to reuse functions by putting them all on the prototype, but it clearly doesn’t work with subclasses that also have constructors.
Combination of inheritance
The composite pattern is to use the stereotype object reuse function and call the parent class constructor in the subclass constructor to achieve localization of variables.
function SupperType(){
this.data=[1.2.3];
}
SupperType.prototype.add=function(num){
this.data.push(num);
}
funciton SubType(name){
this.name=name;
SupperType.call(this);
}
SubType.prototype=new SupperType();
let sub1=new SubType('sub1');
let sub2=new SubType('sub2');
sub1.add(4);
console.log(sub1.data);/ / [1, 2, 3, 4]
console.log(sub2.data);/ / [1, 2, 3]
Copy the code
Composite inheritance looks pretty good, but it’s not perfect. Here we call the superclass constructor twice, the first time when we assign subtype. prototype. This call produces redundant data attributes in the subclass’s prototype. Next we use the parasitic composition pattern to achieve this.
Parasitic combinatorial inheritance
The parasitic combination pattern uses the object. create function used in primitive inheritance to create a prototype Object that inherits only methods from the parent class.
function SupperType(){
this.data=[1.2.3];
}
SupperType.prototype.add=function(num){
this.data.push(num);
}
funciton SubType(name){
this.name=name;
SupperType.call(this);
}
SubType.prototype=Object.create(SupperType.prototype);
let sub1=new SubType('sub1');
let sub2=new SubType('sub2');
sub1.add(4);
console.log(sub1.data);/ / [1, 2, 3, 4]
console.log(sub2.data);/ / [1, 2, 3]
Copy the code
Perfect 👏 👏 👏
instanceof
To expand on a little bit about prototype chains, how does Instanceof determine object types? This is one of the most frequently asked interview questions. Let’s still use the code for 👆 as an example:
function Dog(name){
this.name=name;
}
Dog.prototype={
age:13
}
let dog=new Dog('tom');
consle.log(dog instanceof Dog)//true
Copy the code
The result above is returnedtrue
So, intuitively we might think that this is a judgmentdog
Is the constructor of the objectDog
Function, but we outputdog.constructor===Dog
, the results forfalse
. And of course this is true if we think about 🤔, according to the prototype chain mechanism,dog
Object does not exist by defaultconstructor
Object, then we godog.__proto__
Look it up, because this prototype object is a literal object{age:13}
We know that literal objects areObject
Object, so it’s not hard to get a resultdog.constructor===Object
.
Therefore, we do not determine instanceof directly by determining whether the object’s constructor is equal to the target constructor. Looking closely at the implementation of New we have given above, we see that the constructor Dog and the Dog object are really related to __proto__ and prototype, so we guess, InstanceOf is handled by checking whether the object’s __proto__ is equal to the constructor’s prototype, so let’s experiment:
function Dog(name) {
this.name = name;
}
let proto= {
age: 13
}
Dog.prototype = proto;
let dog = new Dog('tom');
function Cat() {}console.log(dog instanceof Dog); //true
console.log(dog instanceof Cat); //false
Cat.prototype = proto; //Cat's prototype object is equal to Dog's prototype object
console.log(dog instanceof Cat); //true
Copy the code
We declare a Cat function as a constructor and point Cat’s prototype to the Dog prototype object. Then we find that Dog instanceof Cat is true, which verifies our conclusion above: Instanceof is handled by determining whether the object’s __proto__ is equal to the constructor’s prototype, or, more precisely, whether the object’s prototype chain has a prototype equal to the prototype of the constructor being compared. The form such as obj. __proto__. __proto__. __proto__… Can a prototype object equal to a. prototype be found on the chain structure of. Instanof can be realized using the principles above:
function Instanceof(obj,F){
let O = F.prototype;
let proto = obj.__proto__;
while(proto){
if(proto === O){
return true
}
proto = proto.__proto__;
}
return false;
}
Copy the code
conclusion
Through this article, we have introduced the following points in detail:
- Prototype chain mechanism
- The realization of the keyword new
- Primary inheritance
- Combination of inheritance
- Parasitic combinatorial inheritance
- The principle of instanceof
If there are mistakes, but also hope to correct ~ if you help, please feel free to click 👍 ~