JavaScript summary 6.0—Detailed Explanation of Prototype Chain and Handwritten Inheritance
Basic Concept
Prototype
Prototypes are properties of JavaScript functions. Every time a function is created in JavaScript, the JavaScript engine adds an extra property called prototype to the created function.
_proto__
and constructor
properties are unique to objects
The prototype
attribute is unique to function, because function is also a kind of object, so the function also has proto_ and constructor attributes
The proto attribute of the object is equal to the prototype of the new constructor
function foo() {
}
console.log(foo.prototype) //foo {}
var f1 = new foo()
console.log(f1.__proto__ === foo.prototype) //true
constructor
property: a property added by default on the prototype object prototype, pointing to the object's constructor, all functions (the final constructor points to Function
Prototypes allow objects to share properties and methods, simplifying the amount of code
prototype chain
A prototype chain is a chain that connects prototypes in sequence
Each instance object (object) has a private attribute (called proto) pointing to the prototype object of its constructor (prototype). The prototype object also has a prototype object (proto) of its own, layering up until an object's prototype object is null. By definition, null has no prototype and acts as the last link in this prototype chain
var obj = {}
obj.__proto__ = {
}
// prototype chain
obj.__proto__.__proto__ = {
}
obj.__proto__.__proto__.__proto__ = {
value: "3"
}
console.log(obj.value) //3
Order when looking up object properties or methods:
Search for the object itself --> search in the constructor of the object --> the prototype of the object (_proto_) --> the prototype of the constructor (prototype) ---> search in the prototype of the current prototype and so on until the prototype The top of the chain (Object.prototype)
, if it has not been found and the property to be searched for is undefined, and the method to be searched for is an error
The prototype chain is searched up layer by layer, and the prototype of all objects can be traced back to Object in the end. At this time, there is no proto pointing to Object.prototype (top layer)
, so the prototype object at the top of the prototype chain is Object.prototype ( His proto value is null), there are many default properties and methods on this object
var obj = { }
console.log(obj.__proto__) //{}
console.log(Object.prototype)//{}
console.log(obj.__proto__.__proto__) //null
console.log(Object.prototype.__proto__)//null
Handwritten prototype chain inheritance
1. Inheritance through the prototype chain
Prototype chain inheritance is to directly let the prototype of the Child constructor directly point to the Parent object, so that the Parent's property Child object can be found directly from its prototype chain. Core: Child.prototype = new Parent(); //use prototype chain inheritance
Parent class:
// parent class: public properties and methods
function Parent(name, age, friends) {
//Attributes
this.name = name || 'A';
this.friends = friends || [];
this.age = age || 18;
//instance method
this. say = function () {
console.log(this.name + 'say hi');
};
}
//prototype method
Parent.prototype.eating = function() {
console.log(this.name + "eating")
}
Subclass:
// subclass: specific properties and methods
function Child() {
this.sno = 111
}
Child.prototype = new Parent(); //prototype chain inheritance
Child.prototype.studying = function() {
console.log(this.sno + 'studying');
}
var stu = new Child()
console.log(stu.name) //'A'
stu. eating() //A eating
stu.studying()//111 studying
//The subclass successfully inherits the properties and methods of the parent class, and successfully uses its own unique properties and methods
shortcoming:
//Create two stu objects
var stu1 = new Child()
var stu2 = new Child()
// Modify the value in the reference, different subcases will affect each other
stu1. friends. push("B")
console.log(stu1.friends) //B
console.log(stu2.friends) //also B, not []
// cannot pass parameters
var stu3 = new Child("lilei", 112)
- When creating a subclass instance, parameters cannot be passed to the parent class constructor
- The reference properties from the prototype object are shared by all instances, so when multiple instances are created,
if the modified object is a reference type
, different instances will affect each other, because the two instances use the same prototype object
2. Use constructor inheritance
The principle is to use call in the Child constructor to change the point of this, which can realize passing parameters to the parent class, and there is no problem of mutual influence of reference attributes (no prototype is used)
function Child(age) {
Parent.call(this, age)
}
Parent class: the same as the parent class of the prototype chain
Subclass:
// can pass parameters
function Child( name, age, friends) {
Parent. call(this, name, age, friends);
}
var stu = new Child('AB', 20, ['lilei']);
var stu1 = new Child('C', 18, ['li']);
console.log(stu.name)//AB
// Subclass instances will not affect each other
stu.friends.push('lucy');
console.log(stu.friends);//[ 'lilei', 'lucy' ]
console.log(stu1.friends);//['li']
//stu.eating() reports an error, the properties and methods on the prototype chain of the parent function cannot be inherited
shortcoming:
- Only the instance properties and methods of the parent class can be inherited, and the properties/methods on the prototype chain of the parent class cannot be inherited
3. Composition inheritance
Combined inheritance: You can inherit the properties/methods on the prototype chain of the parent class, which is to add the operation of the prototype of the subclass on the basis of inheritance using the constructor, and the constructor of the Parent will be executed once more
function Child(age) {
Parent.call(this, age)
}
Child.prototype = new Parent();
Parent class: the same as the parent class of the prototype chain
Subclass:
function Child(name, age, friends) {
Parent.call(this, name, age, friends);
}
Child.prototype = new Parent();
var stu = new Child('AB', 20, ['lilei']);
console.log(stu.name)//AB
stu.eating() //AB eating
shortcoming:
- The parent class constructor (
new Parent() and Parent.call()
) is called twice, which consumes a little more memory (it feels negligible) - There will be two copies of properties in the supertype: one in the prototype object and one in the subtype instance
(When we call the supertype.call(this, parameter) in the constructor of the subtype, the properties and methods in the supertype will be copied to the subtype. So **the supertype itself inside, we no longer need **)
4. Inheritance through parasitic composition (most recommended)
Parent class: the same as the parent class of the prototype chain
Subclass:
function Child(name, age, friends) {
Parent. call(this, name, age, friends)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
//If you modify the prototype object in the form of an object, you need to use the constructor to point back to the original constructor, that is, repair the constructor
var stu = new Child("F", 18, ["FX"])
console.log(stu) //Parent { name: 'F', friends: [ 'FX' ], age: 18, say: [Function] }
stu. eating()//F eating
console.log(stu.constructor.name) //Child
//If you don't point Child.prototype.constructor back to Child, the value here will be Parent
/*Object.create() actually does two actions to create a new object, using the existing object as the prototype of the newly created object (prototype)*/
/*
Handwritten Object.create()
Object.prototype.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
*/
Summarize
Each of these four handwriting inheritance methods appears to solve the shortcomings of the previous method. It is recommended to connect the four methods to think about learning when writing by hand.