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

  1. The reference values contained in the stereotype are shared across all instances
  2. createChildCannot 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