Prototype and inheritance is more important in JS knowledge, learn and skilled use of our daily development and interview have great help. In the previous article has been detailed about the content of the prototype, learn the prototype, prototype chain, is to master the basic premise of inheritance, interested in the # 2022 learning a TIME JS prototype, prototype chain, here is more than the introduction of the prototype. This article focuses on inheritance, including common inheritance methods in ES5, inheritance of the new class class in ES6, and what the new operator does internally when creating an object.
Prototype chain inheritance:
Those familiar with the prototype and prototype chain know:
- Each object has a __proto__ attribute that points to the prototype object that instantiates the object’s constructor
- When accessing properties or methods of an object that the instance does not have, the object passes through them first
__proto__
Property to find the constructor’s stereotype object along the stereotype chain. - If the constructor’s prototype object is not found either, access to the prototype object continues
__proto__
Property to find all the way to the end of the prototype chainObject.prototype.__proto__
, which is null.
According to the above about the characteristics of the prototype, if the subclass constructor’s prototype object assignment ChengXiang to inherit the parent class constructor instance of the subclass instance, access itself has no property, visit the prototype of the constructor object, this time the prototype of the object has already become the parent class assignment to construct the parent class instance objects, if you haven’t attribute, Accesses the prototype object of the constructor of the parent instance object. This implements prototype chain inheritance.
Code demo:
function Father() { this.name = 'Jack' this.like = ['play', 'sleep'] } Father.prototype.getName = function() { console.log(this.name) } function Son() { this.age = 18 } Son. Prototype = new Father() // Let Son = new Son() // Console.log (son.age) // Son does not have a property, Son.__proto__ = son.prototype = new Father(); son.prototype = new Father(); console.log(son.name) __proto__ = new Father(); son.proto__ = new Father(); So son.prototype. __proto__ points to Father. Prototype, find getName. Console.log (son.getName()) // Attributes of the reference type that a subclass inherits from its parent class, since it is the same value in heap memory, Let son1 = new Son() son1.like.push('eat') let son2 = new Son() console.log(son1.like) // ['play', 'sleep', 'eat'] console.log(son2.like) // ['play', 'sleep', 'eat']Copy the code
Disadvantages:
- In a superclass constructor, attributes of a reference type are shared, and changes made by one subclass affect other subclasses.
- Cannot pass a parameter to a parent class while instantiating a child class.
Borrow constructor inheritance:
In a subclass constructor, the parent class’s constructor is called and the attributes on the parent class instance are inherited by changing the this pointer when the parent constructor is executed.
Code demo:
function Father(name) { this.name = name this.like = ['play', 'sleep'] } Father.prototype.getName = function() { console.log(this.getName) } function Son(name, Age) {Father. Call (this, name); This. age = age} let son = new son ('Jack', Console. log(son.age) // 18 console.log(son.name) // Without inheriting the property console.log(son.getName()) on the superclass prototype // The constructor is re-executed every time the object is instantiated, Let son1 = new Son('Jack', 18) Let son2 = new Son('Mary', 18) son1.like.push('eat') console.log(son1.like) // ['play', 'sleep', 'eat'] console.log(son2.like) // ['play', 'sleep']Copy the code
Because every time a subclass instance is created, it will execute the constructor of the parent class, so the attributes inherited by the subclass from the parent class are unique, which solves the problem of mutual influence when the subclass inherits the attributes of the reference type in the parent class in the prototype chain inheritance.
Disadvantages:
- Unable to inherit attributes from parent prototype chain (son.getName() error)
Combinatorial inheritance:
Combining stereotype chain inheritance with borrowed constructor inheritance is the realization of composite inheritance.
Code demo:
function Father(name) { this.name = name this.like = ['play', 'sleep'] } Father.prototype.getName = function() { console.log(this.name) } function Son(name, age) { Father.call(this, Name) // Call the Father constructor to inherit the attributes of the Father instance object. This.age = age} Son. Prototype = new Father('Jack') Let son = new son ('Jack', 18) console.log(son.age) console.log(son.name) console.log(son.getName())Copy the code
Composite inheritance solves the problem of prototype chain inheritance in which the object of a subclass instance modifies the value of the reference type of the parent class, affecting other subclass instances. At the same time, it solves the problem that the attributes of the prototype object cannot be inherited from the parent class in the borrowing constructor inheritance.
But because the parent constructor is executed twice, some of the attributes will exist on both son and son.__proto__. The console prints son:
Disadvantages:
- Calling the superclass constructor twice incurs unnecessary overhead, and some properties are duplicated on subclass instances and prototypes.
Original type inheritance:
This inheritance is based on the knowledge of prototype and prototype chain. With prototypes, you can create a new object based on an existing object:
function objectCreate(o) {
function F() {}
F.prototype = o
let obj = new F()
return obj
}
Copy the code
Inside the objectCreate function, there is a constructor F that takes the passed object as a prototype of F, instantiates a new object, and returns it. This creates a new object that is modeled after the object passed in. The objectCreate function actually makes a shallow copy of the object passed in, as shown in the following example:
let person = {
name: 'Tom',
like: ['play', 'sleep']
}
let person1 = objectCreate(person)
person1.name = 'Jerry'
person1.like.push('eat')
let person2 = objectCreate(person)
person2.name = 'Jack'
person2.like.push('cry')
console.log(person.like) // ['play', 'sleep', 'eat', 'cry']
Copy the code
The object.create method in ES6 regulates primitive inheritance. This method takes two arguments:
- The object that is the prototype for the new object.
- An object that defines additional properties as a new object.
ObjectCreate is the same as objectCreate when only the first argument is passed
let person = {
name: 'Tom',
like: ['play', 'sleep']
}
let person1 = Object.create(person)
person1.name = 'Jerry'
person1.like.push('eat')
let person2 = Object.create(person)
person2.name = 'Jack'
person2.like.push('cry')
console.log(person.like) // ['play', 'sleep', 'eat', 'cry']
Copy the code
The second argument to object.create is the same as the second argument to the Object.defineProperties method, the descriptor for each property. Properties defined in this way override properties of the same name on the prototype object.
let person = {
name: 'Tom',
like: ['play', 'sleep']
}
let person1 = Object.create(person, {
name: {
value: 'Jerry'
}
})
console.log(person1.name) // Jerry
Copy the code
Parasitic inheritance:
Parasitic inheritance is similar to original inheritance in that it uses an object as a prototype for a new object to enhance the new object in some way.
function objectCreate(o) { function F() {} F.prototype = o return new F() } function createAnother(o) { var obj = SayHi = function() {console.log('Hi')} return obj} let person = {name: 'Tom', like: ['play', 'sleep'] } let person1 = createAnother(person) person1.sayHi()Copy the code
Use createAnother to create an object that not only inherits the attributes on Person, but also has a sayHi method.
Parasitic combinatorial inheritance:
As mentioned above, combinatorial inheritance solves some shortcomings of prototype chain inheritance and borrowed constructor inheritance, and is a relatively perfect inheritance method. But it also has the disadvantage of calling the superclass constructor twice, once when the subclass is created and once inside the subclass constructor. We inherited both the parent instance properties and the parent stereotype properties, but because we called the parent constructor twice, we did unnecessary overhead, and there were duplicate properties on the child instance and the stereotype.
Parasitic combinatorial inheritance solves this problem:
- Inherits attributes on the parent class instance by borrowing constructor inheritance.
- A subclass does not have to point to an instance of its parent class by pointing a stereotype. Using parasitic inheritance directly, create a temporary object with the stereotype of the parent constructor, and then point the stereotype of the subclass to the temporary object.
function objectCreate(o) { function F() {} F.prototype = o return new F() } function extendsPrototype(subType, SuperType) {// Create a temporary object for father, equivalent to: Object. Create(supertype.prototype) let prototype = objectCreate(supertype.prototype) Constructor = subType (); // Subclass (); // Subclass (); // subclass (); // subclass (); // subclass (); // subclass (); // subclass () Father(name) { this.name = name this.like = ['play', 'sleep'] } Father.prototype.getName = function() { console.log(this.name) } function Son(name, age) { Father.call(this, name) this.age = age } extendsPrototype(Son, Father) Son.prototype.getAge = function() { console.log(this.age) } let son = new Son('Tom', 18)Copy the code
In the extendsPrototype function, in order to inherit the attributes of the parent prototype, a temporary object is created through parasitic inheritance and the child’s prototype points to that temporary object, thus inheriting the attributes of the parent prototype. Why not just point the subclass prototype to the superclass prototype? This also gives subclasses access to their parent class’s stereotype properties, like this:
function extendsPrototype(subType, superType) {
subType.prototype = superType.prototype
}
Copy the code
Create a temporary object that inherits from the parent class through parasitic inheritance. The main purpose is to ensure that the inheritance chain will properly look up properties along the prototype chain and use instanceof and isPrototypeOf() to determine the inheritance relationship between objects.
The new operator performs the following steps:
We usually use the new operator to create an object, which is implemented through inheritance. Let’s understand this by implementing a simple new:
function myNew(Con, ... Args) {let obj = {} obj.__proto__ = con. prototype Object.setPrototypeOf(obj, Con.prototype) let result = Con.apply(obj, args) return result instanceof Object ? result : obj }Copy the code
By implementing a simple new function, we know that the new operator mainly does:
- Create a new object.
- The __proto__ attribute of the new object points to the protoobject of the constructor, which is inherited from the protoobject of the constructor.
- Execute the constructor, pointing this to the new object, and inherit the constructor instance properties.
- Determines the result of the constructor execution, returning the object if it is an object, or the new object created in the first step otherwise.
Extends extends in class:
Before the advent of es6’s class class, inheritance in JS was usually implemented using parasitic combinational inheritance, which was complicated and difficult to understand. With the advent of class, JAVASCRIPT inheritance can be written with the same clear and easy to understand steps as implementation inheritance in Java. But class is essentially a constructor, and extends is also a combination of the inheritance methods described above. It can be thought of as a syntactic sugar that makes object prototypes more legible and more like object-oriented programming syntax.
Class vs. constructor:
Let’s start by comparing class to constructor:
class Person {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
Copy the code
The above code is implemented with a constructor:
function Person(name) {
this.name = name
}
Person.prototype.getName() {
return this.name
}
Copy the code
From the above comparison, it can be seen that:
- The class declaration defines a variable Person and assigns the internal constructor to Person
- Methods defined in class are actually bound to the constructor’s prototype object
Class inheritance vs. ES5 inheritance:
class Father { constructor(name) { this.name = name } getName() { return this.name } static getStatic() { Console. log(' I am a static method ')}} Class Son extends Father{constructor(name, constructor) Age) {super(name) this.age = age} getAge() {return this.age}} son.getstatic () let Son = new Son('Tom', 18)Copy the code
The above code is implemented using the constructor:
Function extendsPrototype(subType, superType) { Let prototype = object.create (supertype.prototype) Constructor = subType // subType = new object subType. Prototype = prototype subType.__proto__ = superType } function Father(name) { this.name = name } Father.prototype.getName = function() { Return this.name} Father. GetStatic = function() {console.log(' I am a static method ')} Father. age) { Father.call(this, name) this.age = age } extendsPrototype(Son, Father) Son.prototype.getAge = function() { return this.age } let son = new Son('Tom', 18)Copy the code
Es6 class inheritance is very similar to parasitic combinatorial inheritance:
- The extends keyword assigns the prototype of a subclass to a temporary object that inherits from the parent’s prototype and subclasses it
__proto__
Assign to the parent class (subType.__proto__ = superType
). - To call super, call the parent constructor (father.bind (this)).
But there are some differences from parasitic combinatorial inheritance:
-
__proto__ === Function. Prototype; Son.__proto__ === Father; this is why Son inherits getStatic.
-
In class inheritance, you cannot use this in the constructor until the superclass constructor is called through super. To ensure that the parent class is initialized before the child class. In ES5, we borrow constructor inheritance by first creating the subclass instance of this, and then adding the superclass attribute method to this.
conclusion
This article describes several common inheritance approaches to ES5, as well as their strengths and weaknesses. It also analyzes what the new operator does in terms of inheritance and archetype. Finally, the class inheritance in ES6 and the difference between es5 inheritance are introduced.
If you have any mistakes or questions, please point them out in the comments section. We study together and make progress together!