preface
This article was first published on the author’s personal blog.
If there is any improper expression in the reading process, I hope you don’t hesitate to give me advice.
This article will analyze inheritance from the two aspects of [inheritance implementation/ES5 inheritance implementation] and [ES6 inheritance implementation].
How to implement inheritance/ES5 inheritance implementation
ES5 inheritance methods can be roughly divided into the following six: prototype chain inheritance, embeded constructor inheritance, combination inheritance; Primary inheritance, parasitic inheritance, parasitic combination inheritance.
Prototype chain inheritance
Analysis of the
Reviewing the relationship between the constructor, prototype, and instance in the prototype-prototype chain, we know that the constructor’s Prototype property and the instance’s __proto__ property point to the prototype object, and the prototype object’s constructor property points to the constructor.
When the prototype object of a function becomes an instance object of another function, eg.child.prototype = new Parent() indicates that the prototype has a __proto__ attribute pointing to another prototype. This other stereotype itself also has a constructor attribute pointing to its constructor. This creates a chain of prototypes between the two functions.
instance.__proto__ => Child.prototype
Child.prototype.__proto__ => Parent.prototype
Parent.prototype.constructor => Parent()
function Parent() {
this.ParentProperty = true
}
Parent.prototype.getParentProp = function() {
return this.ParentProperty
}
function Child() {
this.ChildProperty = false
}
// Implement inheritance: A stereotype is an instance of another type
Child.prototype = new Parent()
const instance = new Child()
console.log(instance.getParentProp()) //true
Copy the code
The key points
Child.prototype = new Parent()
Copy the code
With this implementation, __proto__ of different Child instances will refer to instances that agree with Parent.
disadvantages
- The reference values contained in the stereotype are shared across all instances
- create
Child
Cannot be queriedParent()
The ginseng
function Parent() {
this.example = ['aki'.'enjoy']}function Child() {}
Child.prototype = new Parent()
let instance1 = new Child()
instance1.example.push('coding')
let instance2 = new Child()
console.log(instance2.example) // ['aki', 'enjoy', 'coding']
Copy the code
Embezzle constructor inheritance
The emergence of this inheritance is to solve the shortcomings of the above prototype chain inheritance.
Analysis of the
Because functions are simple objects in a particular context, you can use apply() and call() to perform constructors for the context on newly created objects.
function Parent() {
this.example = ['aki'.'enjoy']}function Child() {
// Implement inheritance: execute the constructor in the context of the newly created object
Parent.call(this)}let instance1 = new Child()
instance1.example.push('coding')
let instance2 = new Child()
console.log(instance2.example) // ['aki', 'enjoy']
Copy the code
The key points
function Child(args) {
/ /...
Parent.call(this, args)
}
Copy the code
The advantages and disadvantages
The advantage is that the problem of inheritance of prototype chain is solved.
Attribute values in reference stereotypes are no longer shared by all instances, and arguments can be passed to the superclass constructor in the subclass constructor.
function Parent(name) {
this.name = name
}
function Child() {
// inherit and pass the parameter
Parent.call(this.'aki')
this.age = 20
}
let instance = new Child()
console.log(instance.name) // 'aki'
console.log(instance.age) / / 20
Copy the code
Disadvantages: Methods must be defined in constructors. This leads to the disadvantage of embezzling constructor inheritance — methods are created once each inheritance, so functions cannot be reused.
Only instance property inheritance is implemented, and Parent stereotype methods are not available in Child instances.
Combination of inheritance
Analysis of the
Composite inheritance combines the advantages of both stereotype chain inheritance and embeded constructor inheritance: the stereotype chain inherits properties and methods from the stereotype, and the embeded constructor inherits instance properties.
function Parent(name) {
this.name = name
this.example = ['moon'.'and']
}
Parent.prototype.sayName() = function() {
console.log(this.name)
}
function Child(name, age) {
// Embezzle constructors to inherit instance attributes
Parent.call(this, name)
this.age = age
}
// The prototype chain inheritance method
Child.prototype = new Parent()
let instance1 = new Child('aki'.20)
instance1.example.push('sunrise')
// Instance1 various attributes
console.log(instance1.name) // 'aki'
console.log(instance1.age) / / 20
console.log(instance1.example) // ['moon', 'and', 'sunrise']
instance1.sayName() // 'aki'
let instance2 = new Child('moon'.25)
console.log(instance2.example) // ['moon', 'and']
Copy the code
The key points
function Child(args1, args2) {
/ /...
this.args2 = args2
Parent.call(this, args1)
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
Copy the code
The advantages and disadvantages
Combining the advantages of stereotype chain inheritance and constructors is the most commonly used inheritance pattern in JavaScript.
The problem is that Child instances have Parent instance attributes, and child.__proto__ has the same Parent instance attributes, and all Child instances’ __proto__ points to the same memory address.
Primary inheritance
Analysis of the
Is essentially a mock implementation of ES6’s Object.create.
This inheritance approach is intended to enable “information sharing between objects through prototypes, even without custom functions”.
function objCreate(o) {
fucntion F() {}
F.prototype = o
return new F()
}
Copy the code
The objCreate() function creates a new temporary constructor F, points the prototype of the temporary constructor to o, and returns an instance of F.
Essentially, objCreate() makes a shallow copy of the object passed in.
let person = {
name: 'aki'.eg: ['moon'.'and']}let person1 = objCreate(person)
person1.name = 'person1'
person1.eg.push('sunrise')
let person2 = objCreate(person)
person2.eg.push('kk')
console.log(person2.name) // 'aki'
console.log(person.eg) // ['moon', 'and', 'sunrise', 'kk']
Copy the code
In this example, you actually cloned two Persons.
Note: Person1. name = ‘person1’; person2.name = ‘person1’; person1.name = ‘person1’; You did not change the name value on the stereotype.
disadvantages
Prototype inheritance causes the same problem as prototype-chain inheritance: attribute values that contain reference types always share corresponding values.
Parasitic inheritance
Analysis of the
Create a function that implements inheritance, enhances the object in some way, and returns the object.
function createObj (o) {
var clone = Object.create(o);
clone.sayName = function () {
console.log('hi');
}
return clone;
}
Copy the code
disadvantages
The drawback of this approach is the same as that of a stolen constructor: the method is created every time an object is created, and the function cannot be reused.
Parasitic combinatorial inheritance
Analysis of the
Let’s review combinatorial inheritance:
function Parent(name) {
this.name = name
this.example = ['moon'.'and']
}
Parent.prototype.sayName() = function() {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name) // First call to Parent()
this.age = age
}
Child.prototype = new Parent() // Call Parent() the second time
let instance1 = new Child('aki'.20)
Copy the code
As you can see from the comments above, Parent() is always called twice every time a subclass is instantiated, causing efficiency problems.
To avoid repeated calls to the Parent constructor, instead of using child.prototype = new Parent(), let the
Child.prototype accesses Parent. Prototype.
function inherit(Child, Parent) {
let prototype = objCreate(Parent.prototype) // Create an object
prototype.constructor = Child // Enhance object (override prototype default missing constructor)
Child.prototype = prototype // Assign an object
}
Copy the code
function Parent(name) {
this.name = name
this.example = ['moon'.'and']
}
Parent.prototype.sayName() = function() {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name)
this.age = age
}
inherit(Child, Parent)
let instance1 = new Child('aki'.20)
Copy the code
advantages
The efficiency of this approach is that it calls the Parent constructor only once, and thus avoids creating unnecessary, redundant properties on Parent. Prototype. At the same time, the prototype chain stays the same; Therefore, instanceof and isPrototypeOf can also be used normally. Parasitic combinatorial inheritance is generally considered by developers to be the ideal inheritance paradigm for reference types.
ES6 inheritance implementation
ES6 class
With the introduction of class, you can use the extends keyword directly.
usage
// Contains the parent class of an instance attribute
class Parent {
constructor() {
this.type = 'person'}}// Subclass inheritance
class Student extends Person {
constructor() {
super()}}var student1 = new Student()
student1.type // "person"
student1 instanceof Student // true
student1 instanceof Person // true
student1.hasOwnProperty('type') // true
Copy the code
The principle of analyzing
After compiling with Babel, our code changes as follows
/ / before compilation
class Parent {
constructor() {
this.type = 'person'}} = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =/ / the compiled
var Person = function Person() {
_classCallCheck(this, Person)
this.type = 'person'
}
Copy the code
/ / before compilation
class Student extends Person {
constructor(){
super()}} = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =/ / the compiled
var Student = (function(_Person) {
_inherits(Student, _Person);
This function will be returned as the full Student constructor
function Student() {
// Use detection
_classCallCheck(this, Student);
// the return value of _get can be thought of first as the parent constructor
_get(Object.getPrototypeOf(Student.prototype), 'constructor'.this).call(this);
}
return Student;
})(Person);
/ / _x for Student. Prototype. __proto__
/ / _x2 for 'constructor'
/ / _x3 for this
var _get = function get(_x, _x2, _x3) {
var _again = true;
_function: while (_again) {
var object = _x,
property = _x2,
receiver = _x3;
_again = false;
// Student.prototype.__proto__ null handling
if (object === null) object = Function.prototype;
// The following is a complete copy of the attributes on the parent prototype chain, including the descriptors of the attributes
var desc = Object.getOwnPropertyDescriptor(object, property);
if (desc === undefined) {
var parent = Object.getPrototypeOf(object);
if (parent === null) {
return undefined;
} else {
_x = parent;
_x2 = property;
_x3 = receiver;
_again = true;
desc = parent = undefined;
continue_function; }}else if ('value' in desc) {
return desc.value;
} else {
var getter = desc.get;
if (getter === undefined) {
return undefined;
}
returngetter.call(receiver); }}};function _inherits(subClass, superClass) {
// superClass must be a function type, otherwise an error will be reported
if (typeofsuperClass ! = ='function'&& superClass ! = =null) {
throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
}
// object. create The second argument is used to repair the constructor of the subclass
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false.writable: true.configurable: true}});// object. setPrototypeOf makes a check if it exists, otherwise use __proto__
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
Copy the code
We split the subclass inheritance analysis:
var Student = (function(_Person) {
_inherits(Student, _Person);
function Student() {
_classCallCheck(this, Student);
_get(Object.getPrototypeOf(Student.prototype), 'constructor'.this).call(this);
}
return Student;
})(Person);
Copy the code
Defines the Student constructor, which is a self-executing function that takes a superclass constructor as an argument.
In this function, the inheritance of the stereotype chain attribute of the parent class is implemented.
The essence of the _inherits method above is to have the Student subclass inherit methods from the Person parent stereotype chain. Its implementation principle can be summed up in one sentence:
Student.prototype = Object.create(Person.prototype);
Object.setPrototypeOf(Student, Person)
Copy the code
Isn’t that familiar? Note that Object.create takes the second argument, which in turn implements the Student constructor repair.
[inherits] [constructor] [inherits] [constructor] [inherits] [constructor] [inherits] [constructor] [constructor] [inherits] [constructor] [constructor]
Person.call(this);
Copy the code
We see that Babel compiles class extends into an inheritance of the ES5 composite pattern, which is the essence of JavaScript object-oriented.
Refer to the
books
JavaScript Advanced Programming
The article
In-depth JavaScript | 冴 feather
Front end development core knowledge advanced | LucasHC