JavaScript is an object-oriented language. There is a very classic saying in JavaScript: Everything is an object. Since it is object-oriented, there are three major characteristics of object-oriented: encapsulation, inheritance, and polymorphism. Here we talk about the inheritance of JavaScript, and we will talk about the other two later.
The inheritance of JavaScript is not much the same as that of C++. The inheritance of C++ is based on class, while the inheritance of JavaScript is based on prototypes.
Now the problem is here.
What is the prototype? For prototypes, we can refer to the classes in C++ and save the properties and methods of the object. For example, let's write a simple object
The code copy is as follows:
function Animal(name) {
this.name = name;
}
Animal.prototype.setName = function(name) {
this.name = name;
}
var animal = new Animal("wangwang");
We can see that this is an object Animal, which has an attribute name and a method setName. Note that once the prototype is modified, such as adding a method, all instances of the object will share this method. For example
The code copy is as follows:
function Animal(name) {
this.name = name;
}
var animal = new Animal("wangwang");
At this time, animal only has the name attribute. If we add a sentence,
The code copy is as follows:
Animal.prototype.setName = function(name) {
this.name = name;
}
At this time, animal will also have a setName method.
Inheritance copy - Starting from an empty object, we know that among the basic types of JS, there is a type called object, and its most basic instance is an empty object, that is, the instance generated by calling new Object() directly, or declared with the literal {}. An empty object is a "clean object", with only predefined properties and methods, while all other objects are inherited from empty objects, so all objects have these predefined properties and methods. The prototype is actually an object instance. The meaning of a prototype is: if the constructor has a prototype object A, the instances created by the constructor must be copied from A. Since the instance is copied from object A, the instance must inherit all the properties, methods and other properties of A. So, how is replication implemented? Method 1: Construct Copy Each instance constructed is copied from the prototype, and the new instance occupies the same memory space as the prototype. Although this makes obj1 and obj2 "completely consistent" with their prototypes, it is also very uneconomical - the consumption of memory space will increase rapidly. As shown in the picture:
Method 2: Copy on write This strategy comes from the technology of a consistent deception system: copy on write. A typical example of this kind of fraud is the Dynamic Link Library (DDL) in the operating system, whose memory area is always copied on write. As shown in the picture:
We just need to specify in the system that obj1 and obj2 are equivalent to their prototypes, so when reading, we only need to follow the instructions to read the prototype. When we need to write the properties of an object (such as obj2), we copy a prototype image and make subsequent operations point to the image. As shown in the picture:
The advantage of this method is that we do not need a lot of memory overhead when creating instances and reading attributes. We only use some code to allocate memory when writing the first time, and bring some code and memory overhead. But there will be no such overhead since then, because the efficiency of accessing images and accessing prototypes is consistent. However, for systems that often write operations, this method is not economical than the previous method. Method 3: Reading traversal This method turns the granularity of copy from prototype to member. This method is characterized by copying the member information into the instance image only when writing a member of an instance. When writing object properties, for example (obj2.value=10), an attribute value named value will be generated and placed in the member list of the obj2 object. Look at the picture:
It can be found that obj2 is still a reference to the prototype, and no object instances of the same size as the prototype were created during the operation. In this way, write operations do not lead to a large amount of memory allocation, so memory usage seems economical. The difference is that obj2 (and all object instances) need to maintain a member list. This member list follows two rules: it is guaranteed to be accessed first when read. If no attribute is specified in the object, try to traverse the entire prototype chain of the object until the prototype is empty or the property is found. The prototype chain will be discussed later. Obviously, among the three methods, read traversal is the best performance. Therefore, JavaScript's prototype inheritance is read traversal. constructor People who are familiar with C++ will definitely be confused after reading the code of the top object. It’s easy to understand without the class keyword, after all, there are function keywords, but the keywords are different. But what about the constructor? In fact, JavaScript also has similar constructors, but they are called constructors. When using the new operator, the constructor has been called and this is bound as an object. For example, we use the following code
The code copy is as follows:
var animal = Animal("wangwang");
animal will be undefined. Some people will say that no return value is of course undefined. Then if you change the Animal object definition:
The code copy is as follows:
function Animal(name) {
this.name = name;
return this;
}
Guess what animal is now?
At this time, animal has become window. The difference is that it expands window, so that window has a name attribute. This is because this defaults to window, that is, the top-level variable without specifying it. Only by calling the new keyword can the constructor be called correctly. So, how to avoid the new keyword being missed by the person using it? We can make some minor changes:
The code copy is as follows:
function Animal(name) {
if(!(this instanceof Animal)) {
return new Animal(name);
}
this.name = name;
}
This will be foolproof. The constructor also has another use, indicating which object the instance belongs to. We can use instanceof to judge, but instanceof will return true to ancestor objects and real objects when inheriting, so it is not very suitable. When the constructor is called new, it points to the current object by default.
The code copy is as follows:
console.log(Animal.prototype.constructor === Animal); // true
We can think differently: prototype is valueless at the beginning of the function, and the implementation may be the following logic
// Set __proto__ is a built-in member of the function, get_prototyoe() is its method
The code copy is as follows:
var __proto__ = null;
function get_prototype() {
if(!__proto__) {
__proto__ = new Object();
__proto__.constructor = this;
}
return __proto__;
}
This benefit is that it avoids creating an object instance for every declared function, saving overhead. The constructor can be modified, which will be discussed later. I believe everyone knows what inheritance is based on prototypes, so they don’t show off their IQ lower limit.
There are several types of JS inheritance, here are two types
1. Method 1 This method is most commonly used and has better safety. Let's define two objects first
The code copy is as follows:
function Animal(name) {
this.name = name;
}
function Dog(age) {
this.age = age;
}
var dog = new Dog(2);
It is very simple to construct inheritance, point the prototype of the child object to the instance of the parent object (note that it is an instance, not an object)
The code copy is as follows:
Dog.prototype = new Animal("wangwang");
At this time, dog will have two attributes, name and age. And if the instanceof operator is used for dog
The code copy is as follows:
console.log(dog instanceof Animal); // true
console.log(dog instanceof Dog); // false
This achieves inheritance, but there is a small problem
The code copy is as follows:
console.log(Dog.prototype.constructor === Animal); // true
console.log(Dog.prototype.constructor === Dog); // false
You can see that the object pointed to by the constructor has changed, which does not meet our purpose. We cannot determine who the instance we new belongs to. Therefore, we can add one sentence:
The code copy is as follows:
Dog.prototype.constructor = Dog;
Let's take a look again:
The code copy is as follows:
console.log(dog instanceof Animal); // false
console.log(dog instanceof Dog); // true
done. This method is a link in the maintenance of the prototype chain, which will be explained in detail below. 2. Method 2 This method has its benefits and disadvantages, but the disadvantages outweigh the benefits. Look at the code first
The code copy is as follows:
<pre name="code">function Animal(name) {
this.name = name;
}
Animal.prototype.setName = function(name) {
this.name = name;
}
function Dog(age) {
this.age = age;
}
Dog.prototype = Animal.prototype;
This enables copying of prototype.
The advantage of this method is that it does not require instantiation of objects (compared with method 1), saving resources. The disadvantages are also obvious. In addition to the same problem as above, that is, the constructor points to the parent object, it can only copy the properties and methods declared by the parent object with prototype. That is to say, in the above code, the name attribute of the Animal object cannot be copied, but the setName method can be copied. The most fatal thing is that any modification to the prototype of the child object will affect the prototype of the parent object, that is, the instances declared by both objects will be affected. Therefore, this method is not recommended.
Prototype chain
Anyone who has written about inheritance knows that inheritance can be inherited from multiple levels. And in JS, this forms the prototype chain. The above article also mentioned the prototype chain many times, so, what is the prototype chain? An instance should at least have a proto attribute pointing to the prototype, which is the basis of the object system in JavaScript. However, this property is invisible, and we call it "internal prototype chain" to distinguish it from the "constructor prototype chain" composed of the constructor's prototype (that is, what we usually call "prototype chain"). Let’s first construct a simple inheritance relationship according to the above code:
The code copy is as follows:
function Animal(name) {
this.name = name;
}
function Dog(age) {
this.age = age;
}
var animal = new Animal("wangwang");
Dog.prototype = animal;
var dog = new Dog(2);
As a reminder, as mentioned earlier, all objects inherit empty objects. So, we construct a prototype chain:
We can see that the prototype of the child object points to the instance of the parent object, forming the constructor prototype chain. The inner proto object of the child instance is also an instance pointing to the parent object, forming an internal prototype chain. When we need to find a property, the code is similar to
The code copy is as follows:
function getAttrFromObj(attr, obj) {
if(typeof(obj) === "object") {
var proto = obj;
while(proto) {
if(proto.hasOwnProperty(attr)) {
return proto[attr];
}
proto = proto.__proto__;
}
}
return undefined;
}
In this example, if we look for the name attribute in dog, it will be searched in the member list in dog. Of course, it will not be found, because now the member list of dog is only the item age. Then it will continue to search along the prototype chain, that is, the instance pointed to by .proto, that is, in animal, find the name attribute and return it. If the search is a property that does not exist, when it cannot be found in animal, it will continue to search with .proto, find an empty object, and then continue to search with .proto, and the .proto of the empty object points to null, looking for exit.
Maintenance of prototype chains We raised a question when we talked about prototype inheritance just now. When using method 10 to construct inheritance, the constructor of the child object instance points to the parent object. The advantage of this is that we can access the prototype chain through the constructor attribute, and the disadvantages are also obvious. An object whose instance should point to itself, i.e.
The code copy is as follows:
(new obj()).prototype.constructor === obj;
Then, after we rewrite the prototype properties, the constructor of the instance generated by the child object does not point to itself! This goes against the original intention of the constructor. We mentioned a solution above:
The code copy is as follows:
Dog.prototype = new Animal("wangwang");
Dog.prototype.constructor = Dog;
It seems that nothing is wrong. But in fact, this brings up a new problem because we will find that we cannot go back to the prototype chain because we cannot find the parent object, and the .proto attribute of the internal prototype chain is inaccessible. Therefore, SpiderMonkey provides an improvement: add a property named __proto__ to any created object, which always points to the prototype used by the constructor. In this way, any modification of constructor will not affect the value of __proto__, making it convenient to maintain the constructor.
However, there are two more problems:
__proto__ is rewritable, which means there are still risks when using it
__proto__ is a special processing of spiderMonkey and cannot be used in other engines (such as JScript).
There is another way for us to maintain the constructor properties of the prototype and initialize the constructor properties of the instance within the subclass constructor function.
The code is as follows: Rewrite the child object
The code copy is as follows:
function Dog(age) {
this.constructor = arguments.callee;
this.age = age;
}
Dog.prototype = new Animal("wangwang");
In this way, the constructor of all instances of child objects correctly points to the object, while the constructor of the prototype points to the parent object. Although this method is relatively inefficient because the constructor attribute must be rewrited every time the instance is constructed, there is no doubt that this method can effectively resolve the previous contradictions. ES5 takes this into account and completely solves this problem: Object.getPrototypeOf() can be used at any time to obtain the real prototype of an object without accessing the constructor or maintaining the external prototype chain. Therefore, as mentioned in the previous section, we can rewrite it as follows:
The code copy is as follows:
function getAttrFromObj(attr, obj) {
if(typeof(obj) === "object") {
do {
var proto = Object.getPrototypeOf(dog);
if(proto[attr]) {
return proto[attr];
}
}
while(proto);
}
return undefined;
}
Of course, this method can only be used in browsers that support ES5. In order to be backward compatibility, we still need to consider the previous method. A more suitable method is to integrate and encapsulate these two methods. I believe readers are very good at this, so I won’t be ugliness here.