This article starts from the core prototype chain of JavaScript inheritance, and introduces some ways to implement inheritance in ES5 and ES6, and the principles behind it.

Prototype chain

Prototype-based languages

JavaScript is often described as a prototype-based language — each object has a prototype object from which the object is a template and inherits methods and properties. A stereotype object may also have a stereotype from which it inherits methods and properties, layer by layer, and so on. This relationship, often referred to as the Prototype chain, explains why one object has properties and methods defined in other objects. To be precise, these properties and methods are defined on prototype properties above the Object’s constructor functions, not on the Object instance itself. In JavaScript, a link is made between the object instance and its constructor (which is the __proto__ property, derived from the constructor’s prototype property), and then those properties and methods are found in the constructor by going up the prototype chain.

It is important to understand the difference between an Object’s prototype (which can be obtained via object.getProtoTypeof (obj) or the deprecated __proto__ attribute) and the constructor’s prototype attribute. ** The former is a property on every instance, and the latter is a property of the constructor. Object.getprototypeof (new Foobar()) === Foobar. Prototype

And class-based languages

In traditional OOP, a “class” is first defined, and then all properties and methods defined in the class are copied into the instance when an object instance is created. In JavaScript, however, we look for methods or properties on the prototype chain by reference, so this is probably better known as a delegate.

Core Diagram (Basic)



Let’s start with a simple version of the diagram, with a few key questions

  • Function of theprototypeProperty refers to an object that calls theThe constructorOf the instance createdThe prototype(Hence the instance prototype)
  • The instance object has one__proto__Attributes, pointing to himThe prototype
  • There is one for each instance prototypeconstructorAttribute points to the associatedThe constructor

Why do some objects have themprototypeSome didn’t

JavaScript is divided into function objects and normal objects. Each object has a __proto__ attribute, but only function objects have a Prototype attribute

How does JavaScript access properties or methods on the prototype chain

When calling the valueOf method (Person.valueof ()) on a Person inherited from an Object, the following happens:

  • The browser first checks,personWhether the object has an availablevalueOf()Methods.
  • If not, the browser checkspersonThe prototype object of the object (i.ePersonconstructionalprototypeProperty is availablevalueof()Methods.
  • If not, the browser checksPerson()The prototype object of the object to which the constructor’s Prototype property points (i.eObjectWhether the prototype object pointed to by the constructor’s property) is availablevalueOf()Methods. There’s this method, and that method gets called.

whyperson.constructor === Person

The constructor attribute on Person does not exist. The constructor attribute on Person does not exist. The constructor attribute on Person does not exist. Person. ‘ ‘constructor () {constructor ();} person.constructor === Person.prototype.constructor

Core Diagram (Advanced)




Now let’s switch to a more complicated one and addFunctionIt becomes complicated.

What’s at the top of the prototype chain

  • Object.prototypeRoot of the prototype chain, which points tonullObject.prototype === null)

The Object and the Function

  • For Function
    • Function.prototype.__proto__ === Object.prototypeHere the connection between Function and Object is establishedA function is an object
    • Object.__proto__-> Function.prototype Object is a function (constructor)
    • Function.__proto__ === Function.prototypeHere’s where Function is different from everyone else. And when you look it up, you start fromFunction.prototypeTo check theObject.prototypeGo through the prototype chain
  • For the Object
    • This explains whyObject.prototype.valueOf === Object.valueOfObject. Prototype properties can be accessed directly on Object, but not on PersonPerson.prototypeProperty ofObject.__proto__-> Function.prototype

Function.prototype.__proto__->Object.prototype

The egg problem

  • Which came first, the chicken or the egg? existingObjectOr theFunction
    • Object instanceof Function === true
    • Function instanceof Object === true
  • Core:Object.prototype.__proto__ === nullThe original one was artificially prescribedrootPrototypeSo it can be considered to have come firstObject

Inheritance in ES5

The prototype chain mechanism in JavaScript was briefly described above, so how can inheritance be implemented based on this mechanism

Prototype chain inheritance

/ / father
function Parent () {
  this.name = 'foo';
}
// Add methods to the prototype chain
Parent.prototype.getName = function () {
  console.log(this.name);
  return this.name
}
/ / child
function Child () {

}
Child.prototype = new Parent();

const child1 = new Child();
console.log(child1.getName()) // foo
Copy the code

The simplest inheritance

  • Disadvantages:
    • Properties of reference types are shared by all instances
      • That is, modifying a reference type property on the stereotype chain affects all children
    • When creating an instance of Child, you cannot pass an argument to the Parent

Borrow constructor inheritance

/ / father
function Parent (names) {
  this.names = names;
}
/ / child
function Child (names) {
  Parent.call(this, names);
}


const child1 = new Child([]);
child1.names.push('test');
console.log(child1.names); // ["test"]

const child2 = new Child([]);
console.log(child2.names); / / []
Copy the code
  • advantages
    • Properties of reference types are prevented from being shared by all instances
    • You can pass an argument to a Parent in a Child
  • Disadvantages:
    • Methods are defined in the constructor and are created each time an instance is created.

3. Combinatorial inheritance often used.

/ / father
function Parent (name) {
  this.name = name;
  this.colors = ['red'.'blue'.'green'];
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
/ / child
function Child (name, age) {
  // Execute the Parent constructor
  Parent.call(this, name);
  this.age = age;
}
// Modify prototype to the parent element instance. To have child element instances use parent element attributes via __proto__
// The Parent constructor is executed one more time
Child.prototype = new Parent();
// correct constructor to ensure child.constructor === child
Child.prototype.constructor = Child;

const child1 = new Child('foo'.'18');
child1.colors.push('black');
console.log(child1.name); // foo
console.log(child1.age); / / 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

const child2 = new Child('bar'.'20');
console.log(child2.name); // bar
console.log(child2.age); / / 20
console.log(child2.colors); // ["red", "blue", "green"]
Copy the code
  • disadvantages
    • The parent constructor is called twice, resulting inprototypeDuplicate attributes on and instances

Primary inheritance

function createObj(o) {
  function F(){}
  F.prototype = o;
  return new F();
}
Copy the code

Create is a mock implementation of ES5 Object.create that uses the passed Object as a prototype for the created Object.

  • disadvantages
    • Attribute values that contain reference types always share corresponding values, as with stereotype chain inheritance

Parasitic inheritance

Create a function that encapsulates only the inheritance process, internally does the enhancement object in some form, and finally returns the object.

function createObj (o) {
  const clone = Object.create(o);
  clone.sayName = function () {
    console.log('hi');
  }
  return clone;
}
Copy the code
  • disadvantages
    • Like borrowing the constructor pattern, methods are created each time an object is created.

3. Parasitic combinatorial inheritance often used.

Core: Prototype = new Parent(); parent.prototype = new Parent(); To create a __proto__ pointer, now we’ll modify Child’s prototype to point to Parent’s prototype

// Code encapsulation
function object(o) {
  // Use a "clean" function that does not execute the Parent constructor
  function F() {}
  F.prototype = o;
  return new F();
}
function prototype(child, parent) {
  const prototype = object(parent.prototype);
  / / correct consturctor
  prototype.constructor = child;
  __proto__ = parent.prototype
  child.prototype = prototype;
}

// Business code
function Parent (name) {
  this.name = name;
  this.colors = ['red'.'blue'.'green'];
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
function Child (name, age) {
  Parent.call(this, name);
  this.age = age;
}

// When we use:
prototype(Child, Parent);
const child1 = new Child('foo'.'18');
console.log(child1);
Copy the code
  • advantages
    • The Parent constructor is called only once
    • prototypeThere are no duplicate attributes on
    • The prototype chain remains the same and works with Instanceof and isPrototypeOf



The code relationship is shown above

Inheriting static methods

None of the previous inheritance methods implement static method inheritance on constructors, whereas in ES6 classes, subclasses can inherit static methods from their parent classes. We can implement static method inheritance through the object.setPrototypeof () method.

function prototype(child, parent) {
  const prototype = Object.create(parent.prototype);
  / / correct consturctor
  prototype.constructor = child;
  __proto__ = parent. Prototype
  child.prototype = prototype;
}

// Business code
function Parent (name) {
  this.name = name;
  this.colors = ['red'.'blue'.'green'];
}
// Static method
Parent.staticFn = function () {
  return "Parent";
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
function Child (name, age) {
  Parent.call(this, name);
  this.age = age;
}
// Inherit static methods
Object.setPrototypeOf(Child, Parent);

// When we use:
prototype(Child, Parent);
const child1 = new Child('foo'.'18');
console.log(child1);
Child.staticFn()
Copy the code

Extension –Object.create()

The object.create () method creates a new Object, using an existing Object to provide the __proto__ of the newly created Object. That is, create a new object whose __proto__ refers to an existing object

parameter

Object. The create (proto, [propertiesObject])

  • protoThe prototype object of the newly created object.
  • propertiesObject[Optional] You need to pass in an object whose attribute type is referencedObject.defineProperties()The second argument to the data descriptor –configurable,enumerable,value,writable And accessor descriptor –get,set). If this parameter is specified and notundefined, of the passed objectOwn enumerable propertiesThe newly created object is added the specified property value and corresponding property descriptor (that is, a property defined by itself, rather than an enumerated property on its stereotype chain).

Usage scenarios

  • When instance stereotypes are needed, but constructors do not need to be executed (such as parasitic composite inheritance)
    • o = new Constructor()o = Object.create(Constructor.prototype)The difference is that the latter does not execute the constructor
  • Create a pure object
    • Object.create(null)

Polyfill

Object.create = (proto, propertiesObject) = > {
  if (typeofproto ! = ='object' && typeofproto ! = ='function') {
    throw new TypeError('Object prototype may only be an Object: ' + proto);
  }
  if (propertiesObject === null) throw 'TypeError'
  
  function F () {}
  F.prototype = proto
  const o = new F()
  
  // Add attributes
  if (typeofpropertiesObject ! = ='undefined') {
    Object.defineProperties(o, propertiesObject)
  }
  // If proto is null, __proto__ needs to be removed
  if (proto === null) o.__proto__ = null
  // Return a new object
  return o
}
Copy the code

Extension –Object.setPrototypeOf()

Sets the Prototype of a given object (that is, the internal [[Prototype]] property, that is, __proto__) to another object or null. Minor differences from Object.create

  • Object.createThat’s creating an object, that’s creating an object__proto__It points to an existing object
  • Object.setPrototypeOf()Is a prototype that sets a specified object

That is, Object. Create has one more layer of objects

parameter

Object.setPrototypeOf(obj, prototype)

  • objThe object for which you want to set the prototype.
  • prototypeA new prototype of the object (an object ornull).

Usage scenarios

  1. Inheriting static methods
    1. As above,

Polyfill

Object.prototype.setPrototypeOf = function(obj, proto) {
  if(obj.__proto__) {
    obj.__proto__ = proto;
    return obj;
  } else {
    // If you want to return prototype of Object.create(null):
    const Fn = function() {
      for (const key in obj) {
        Object.defineProperty(this, key, {
          value: obj[key], }); }}; Fn.prototype = proto;return newFn(); }}Copy the code

Inheritance – Class syntax sugar in ES6

The addition of classes in ES6 simplifies implementation inheritance. Let’s look at the basics first

Class expressions and class declarations

In fact, classes are “special functions.” Just like function expressions and function declarations that you can define, class syntax has two components: class expressions and class declarations.

  • Class declaration: One way to define a class is to use a class declaration. To declare a class, you can use the class name with the class keyword
  • Class expressions: Class expressions can be named or unnamed. The name of a named class expression is the local name of the class body.
/ / the class declaration
class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width; }}// Class expression - anonymous class
let Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width; }};// Class expression - named class
let Rectangle = class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width; }};Copy the code

Class body and method definition

Class elements have the following attributes

  • Constructor:constructorMethod is a special method used to create and initialize a methodclassObject created
    • Note: All methods defined inside a class are not enumerable, i.e
// class
class Person {
  constructor(name) {
    this.name = name; }}// ES5
function Person(name) {
  this.name = name;
}
Copy the code
  • Prototype method
    • Note: All methods defined inside a class are not enumerable
// class
class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    return 'hello, I am ' + this.name; }}Object.keys(Person.prototype); / / []
Object.getOwnPropertyNames(Person.prototype); // ["constructor", "sayHello"]

// ES5
function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function () {
  return 'hello, I am ' + this.name;
};
Object.keys(Person.prototype); // ['sayHello']
Object.getOwnPropertyNames(Person.prototype); // ["constructor", "sayHello"]
Copy the code
  • Static method:staticThe keyword is used to define a static method of a class. Calling a static method does not require instantiation of the class, but you cannot call a static method from a class instance. The analogy is with the methods defined on constructor objects in ES5.
// class
class Person {
  static sayHello() {
    return 'hello';
  }
}
Person.sayHello() // 'hello'
const foo = new Person();
foo.sayHello(); // TypeError: foo.sayHello is not a function

// ES5
function Person() {}
// Not on the prototype chain
Person.sayHello = function() {
    return 'hello';
};
Person.sayHello(); // 'hello'
var foo = new Person();
kevin.sayHello(); // TypeError: foo.sayHello is not a function
Copy the code
  • Instance attributes
    • Instance attributes must be defined in class methods. (But there is a proposal for Stag 3 that can be written directly in the class)
    • Static or prototypical data attributes must be defined outside the class definition.
// class
class Person {
  constructor() {
    this.state = {
      count: 0}; }}// New Field declarations
class Person {
  state = {
    count: 0
  };
}
// ES5
function Person() {
  this.state = {
    count: 0
  };
}
Copy the code
  • Static attributes
    • Static public fields can be used when you want to create a property that has only one copy per class, but not in every instance of the class you create. Static public fields are not repeatedly initialized in subclasses
// class
class Person {
  static name = 'foo';
}

// ES5
function Person() {};
Person.name = 'foo';
Copy the code
  • gettersetter
    • As in ES5, you can use the get and set keywords inside a “class” to set the store and value functions for an attribute and intercept the access behavior of that attribute
// class
class Person {
  get name() {
    return 'bar';
  }
  set name(newName) {
    console.log('new name is: ' + newName)
  }
}
let person = new Person();
person.name = 'foo';
// New name is: foo
console.log(person.name);
// bar

// ES5
function Person(name) {}
Person.prototype = {
  get name() {
    return 'bar';
  },
  set name(newName) {
    console.log('new name is: ' + newName)
  }
}
let person = new Person();
person.name = 'foo';
// New name is: foo
console.log(person.name);
// bar
Copy the code
  • Private properties/methods
    • To the property/method#I can make it private,#Is part of the name and is also used for access and declaration.
    • Private fields can only be predefined in the field declaration and cannot be created by assigning values later
class Rectangle {
  #height = 0;
  #width;
  constructor(height, width) {
    this.#height = height;
    this.#width = width;
  }
	static #privateStaticMethod() {
    return 42; }}// The closure /Symbol/WeakMap can be used to implement WeakMap
Copy the code

How does Babel compile your Class

The following compilation is from Babel Try it Out

Only constructors

// Input
class Person {
  constructor(name) {
    this.name = name; }}// Output
"use strict";
// A distinction is made between cases where the environment supports and does not support Symbol
// In the presence of Symbol, the left instanceof right is actually right[symbol.hasinstance](left), and the method can be overridden inside the class
function _instanceof(left, right) {
  if(right ! =null && typeof Symbol! = ="undefined" && right[Symbol.hasInstance]) {
    return!!!!! right[Symbol.hasInstance](left);
  } else {
    return left instanceofright; }}// _classCallCheck checks if Person is called using new. The class must use new to avoid an error.
// When called with var person = person (), this points to the window, so instance instanceof Constructor will be false, as ES6 requires.
function _classCallCheck(instance, Constructor) {
  if(! _instanceof(instance, Constructor)) {throw new TypeError("Cannot call a class as a function"); }}var Person = function Person(name) {
  _classCallCheck(this, Person);
  this.name = name;
};
Copy the code

Instance properties, static properties, private properties

// Input
class Person {
  // Instance properties
  foo = 'foo';
	// Static attributes
	static bar = 'bar';
	// Private attributes
	#test = 'test';
	constructor(name) {
    this.name = name; }}// Output
"use strict";
function _instanceof(left, right) { if(right ! =null && typeof Symbol! = ="undefined" && right[Symbol.hasInstance]) { return!!!!! right[Symbol.hasInstance](left); } else { return left instanceofright; }}function _classCallCheck(instance, Constructor) { if(! _instanceof(instance, Constructor)) {throw new TypeError("Cannot call a class as a function"); }}// defineProperty Modifies/sets the attribute
function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true.configurable: true.writable: true
    });
  } else {
    obj[key] = value;
  }
  return obj;
}
// Implement private variables through weakMap, as described in the extensions below
var _test = new WeakMap(a);var Person = function Person(name) {
  _classCallCheck(this, Person);
  _defineProperty(this."foo".'foo');
  _test.set(this, {
    writable: true.value: 'test'
  });
  this.name = name;
};
_defineProperty(Person, "bar".'bar');
Copy the code

We can clearly find that:

  • Instance attributes passdefinePropertySet to the instance in the constructor
  • Static attributesdefinePropertyThe constructor is set outside the constructor
  • Private and instance properties/static properties are independent and implemented via weakMap

Instance methods, static methods, private methods, getters/setters

// Input
class Person {
  #hello = 'hello'
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    return 'hello, I am ' + this.#privateSayHello();
  }
  static onlySayHello() {
    return 'hello';
  }
  #privateSayHello() {
  	return this.#hello;
  }
  get name() {
    return 'foo';
  }
  set name(newName) {
    console.log('new name is: '+ newName); }}// Output
"use strict";
function _instanceof(left, right) { if(right ! =null && typeof Symbol! = ="undefined" && right[Symbol.hasInstance]) { return!!!!! right[Symbol.hasInstance](left); } else { return left instanceofright; }}function _classCallCheck(instance, Constructor) { if(! _instanceof(instance, Constructor)) {throw new TypeError("Cannot call a class as a function"); }}// 1. Set the value to non-enumerable, which is consistent with the above specification.
// 2. For getter setters, you need to set them to unwritable
function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor); }}// Add instance methods to the prototype chain and static methods to the constructor
function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor;
}

// The following three are used to obtain private attributes
function _classPrivateFieldGet(receiver, privateMap) {
    var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get");
    return _classApplyDescriptorGet(receiver, descriptor);
}
// Protect the call to get the constructor binding method in weakMap
function _classExtractFieldDescriptor(receiver, privateMap, action) {
    if(! privateMap.has(receiver)) {throw new TypeError("attempted to " + action + " private field on non-instance");
    }
    return privateMap.get(receiver);
}
// Call the method to get the value of the private property
function _classApplyDescriptorGet(receiver, descriptor) {
    if (descriptor.get) {
        return descriptor.get.call(receiver);
    }
    return descriptor.value;
}

// Call private methods
// When calling, check whether there is such weakMap on the instance, and execute the corresponding method if so
function _classPrivateMethodGet(receiver, privateSet, fn) {
    if(! privateSet.has(receiver)) {throw new TypeError("attempted to get private field on non-instance");
    }
    return fn;
}

var _hello = new WeakMap(a);var _privateSayHello = new WeakSet(a);var Person = /*#__PURE__*/function () {
  function Person(name) {
    _classCallCheck(this, Person);
    _privateSayHello.add(this);
    // Set the value of the private property
    _hello.set(this, {
      writable: true.value: 'hello'
    });
    this.name = name;
  }
  // The first argument is a constructor, the two arguments are instance methods, and the third argument is static methods
  _createClass(Person, [{
    key: "sayHello".value: function sayHello() {
      return 'hello, I am ' + _classPrivateMethodGet(this, _privateSayHello, _privateSayHello2).call(this); }}, {key: "name".get: function get() {
      return 'foo';
    },
    set: function set(newName) {
      console.log('new name is: '+ newName); }}], [{key: "onlySayHello".value: function onlySayHello() {
      return 'hello'; }}]);returnPerson; } ();function _privateSayHello2() {
  return _classPrivateFieldGet(this, _hello);
}
Copy the code

A few things to note here

  • When a method is set, it makes the value of the set non-enumerable, as required by the specification above.
  • forgetter/setter, is not writable
  • Private methods also rely onweakMapImplementation, but the method is defined outside the constructor, through the constructor andweakMapLink, and note the difference between private method and private variable values

How does Babel let you inherit through Class

Now that we’ve looked at how the various properties and methods of a Class are compiled, how do we use them to implement inheritance

Only constructors

Note that to use this in a subclass’s constructor, you must call the superclass’s constructor once, namely super() (similar to parent.call (this) in ES5). This is because the subclass does not have its own This object, but inherits the parent’s This object and then processes it.

// Input - parasitic combinatorial inheritance
class Parent {
  constructor(name) {
    this.name = name; }}class Child extends Parent {
  constructor(name, age) {
    super(name);
    this.age = age; }}const child1 = new Child('foo'.'18');

// Output
"use strict";
function _typeof(obj) {
  "@babel/helpers - typeof";
  if (typeof Symbol= = ="function" && typeof Symbol.iterator === "symbol") {
    _typeof = function _typeof(obj) {
      return typeof obj;
    };
  } else {
    _typeof = function _typeof(obj) {
      return obj && typeof Symbol= = ="function" && obj.constructor === Symbol&& obj ! = =Symbol.prototype ? "symbol" : typeof obj;
    };
  }
  return _typeof(obj);
}
// Focus, the core of inheritance
function _inherits(subClass, superClass) {
  // Extend must be a function or null
  if (typeofsuperClass ! = ="function"&& superClass ! = =null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  // Similar to ES5's parasitic combinatorial inheritance, use object. create to set the __proto__ attribute of the protoclass to the prototype attribute of its parent class
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      writable: true.configurable: true}});// Set the __proto__ attribute of a subclass to the parent class, which gives the subclass access to static methods on the parent class
  if (superClass) _setPrototypeOf(subClass, superClass);
}
// The utility method sets the __proto__ of an object
function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
    o.__proto__ = p;
    return o;
  };
  return _setPrototypeOf(o, p);
}
// Create a super to call the parent element constructor, dealing mainly with its return type
function _createSuper(Derived) {
  var hasNativeReflectConstruct = _isNativeReflectConstruct();
  return function _createSuperInternal() {
    // Get the prototype, which is Parent (inherited earlier)
    var Super = _getPrototypeOf(Derived),
        result;
    if (hasNativeReflectConstruct) {
      // Reflect goes high-end
      // The constructor property of the prototype object for the newly created object
      var NewTarget = _getPrototypeOf(this).constructor;
      result = Reflect.construct(Super, arguments, NewTarget);
    } else {
      // The environment without Reflect is called as a constructor
      result = Super.apply(this.arguments);
    }
    // Check the return value
    return _possibleConstructorReturn(this, result);
  };
}
// Used to handle constructor return values -- The specification allows active return of an object, method, or this inside a constructor
function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  }
  return _assertThisInitialized(self);
}
// Check if this exists
function _assertThisInitialized(self) {
  if (self === void 0) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }
  return self;
}
// Utility methods, whether native Reflect is supported
function _isNativeReflectConstruct() {
  if (typeof Reflect= = ="undefined" || !Reflect.construct) return false;
  if (Reflect.construct.sham) return false;
  if (typeof Proxy= = ="function") return true;
  try {
    Boolean.prototype.valueOf.call(Reflect.construct(Boolean[],function() {}));
    return true;
  } catch (e) {
    return false; }}// The tool method returns the prototype of the specified object
function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
    return o.__proto__ || Object.getPrototypeOf(o);
  };
  return _getPrototypeOf(o);
}
function _instanceof(left, right) { if(right ! =null && typeof Symbol! = ="undefined" && right[Symbol.hasInstance]) { return!!!!! right[Symbol.hasInstance](left); } else { return left instanceofright; }}function _classCallCheck(instance, Constructor) { if(! _instanceof(instance, Constructor)) {throw new TypeError("Cannot call a class as a function"); }}var Parent = function Parent(name) {
  _classCallCheck(this, Parent);
  this.name = name;
};

var Child = /*#__PURE__*/function (_Parent) {
  // Inherit the prototype chain
  _inherits(Child, _Parent);
  // Create a super
  var _super = _createSuper(Child);
  function Child(name, age) {
    var _this;
    _classCallCheck(this, Child);
    // call super to get this
    _this = _super.call(this, name);
    _this.age = age;
    return _this;
  }
  return Child;
}(Parent);

var child1 = new Child('foo'.'18');
Copy the code

The core of this piece is actually two: how to deal with super and how to deal with inheritance:

  • How to handle inheritance
    • Defensively, extend’s inheritance target must be a function or NULL
    • The core of inheritance is similar to ES5’s parasitic combinatorial inheritance, usingcreatecomplete
    • Class-based inheritance in ES6, a subclass can inherit static methods from its parent Class, so you need to let the subclass’s__proto__Points to the parent class
// Focus, the core of inheritance
function _inherits(subClass, superClass) {
  // Extend must be a function or null
  if (typeofsuperClass ! = ="function"&& superClass ! = =null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  // Similar to ES5's parasitic combinatorial inheritance, use object. create to set the __proto__ attribute of the protoclass to the prototype attribute of its parent class
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      writable: true.configurable: true}});// Set the __proto__ attribute of a subclass to the parent class, which gives the subclass access to static methods on the parent class
  if (superClass) _setPrototypeOf(subClass, superClass);
}
Copy the code
  • How to deal withsuper
    • In essence,superSimilar to theParent.call(this)
    • Note the extra processing of the return value
// Create a super to call the parent element constructor, dealing mainly with its return type
function _createSuper(Derived) {
  var hasNativeReflectConstruct = _isNativeReflectConstruct();
  return function _createSuperInternal() {
    // Get the prototype, which is Parent (inherited earlier)
    var Super = _getPrototypeOf(Derived),
        result;
    if (hasNativeReflectConstruct) {
      // Reflect goes high-end
      // The constructor property of the prototype object for the newly created object
      var NewTarget = _getPrototypeOf(this).constructor;
      result = Reflect.construct(Super, arguments, NewTarget);
    } else {
      // The environment without Reflect is called as a constructor
      result = Super.apply(this.arguments);
    }
    // Check the return value
    return _possibleConstructorReturn(this, result);
  };
}
// Used to handle constructor return values -- The specification allows active return of an object, method, or this inside a constructor
function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  }
  return _assertThisInitialized(self);
}
Copy the code

Inherits from built-in objects

// Input
class Child extends Array {
  constructor(value) {
    super(value); }}const child1 = new Child(1);
// Output
"use strict";
function _instanceof(left, right) { if(right ! =null && typeof Symbol! = ="undefined" && right[Symbol.hasInstance]) { return!!!!! right[Symbol.hasInstance](left); } else { return left instanceofright; }}function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol= = ="function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol= = ="function" && obj.constructor === Symbol&& obj ! = =Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _classCallCheck(instance, Constructor) { if(! _instanceof(instance, Constructor)) {throw new TypeError("Cannot call a class as a function"); }}function _inherits(subClass, superClass) { if (typeofsuperClass ! = ="function"&& superClass ! = =null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true.configurable: true}});if (superClass) _setPrototypeOf(subClass, superClass); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this.arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _wrapNativeSuper(Class) {
  // Map-based cache
  var _cache = typeof Map= = ="function" ? new Map() : undefined;
  _wrapNativeSuper = function _wrapNativeSuper(Class) {
    // protect, and return directly if there is no, or non-native function
    if (Class === null| |! _isNativeFunction(Class))return Class;
    if (typeofClass ! = ="function") {
      throw new TypeError("Super expression must either be null or a function");
    }
    if (typeof_cache ! = ="undefined") {
      if (_cache.has(Class)) return _cache.get(Class);
      _cache.set(Class, Wrapper);
    }

    function Wrapper() {
      // A new instance is generated with the parent constructor
      return _construct(Class, arguments, _getPrototypeOf(this).constructor);
    }
    // Attach the parent's prototype method
    Wrapper.prototype = Object.create(Class.prototype, {
      constructor: {
        value: Wrapper,
        enumerable: false.writable: true.configurable: true}});// Fix __proto__ pointing
    return _setPrototypeOf(Wrapper, Class);
  };
  // This returns an instance of the parent class
  return _wrapNativeSuper(Class);
}
// The utility function polyfill for reflect. construct
function _construct(Parent, args, Class) {
    if (_isNativeReflectConstruct()) {
        _construct = Reflect.construct;
    } else {
        _construct = function _construct(Parent, args, Class) {
            var a = [null];
            a.push.apply(a, args);
            var Constructor = Function.bind.apply(Parent, a);
            var instance = new Constructor();
            if (Class) _setPrototypeOf(instance, Class.prototype);
            return instance;
        };
    }
    return _construct.apply(null.arguments);
}
function _isNativeReflectConstruct() { if (typeof Reflect= = ="undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy= = ="function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean[],function () {})); return true; } catch (e) { return false; }}function _isNativeFunction(fn) {
    return Function.toString.call(fn).indexOf("[native code]")! = = -1;
}
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

var Child = /*#__PURE__*/function (_Array) {
  _inherits(Child, _Array);
  var _super = _createSuper(Child);
  function Child(value) {
    _classCallCheck(this, Child);
    return _super.call(this, value);
  }
  returnChild; } (/*#__PURE__*/_wrapNativeSuper(Array));

var child1 = new Child('foo');
Copy the code

This is a good solution. You’ll notice that in the previous ES5 process, we didn’t deal with inherited built-in objects such as Array, Date, etc. This is because the Parent constructor was called and call was used to change this to point to the subclass instance implementation (Parent. Call (this, foo)). But for native constructors, there are several cases:

  • Some will ignoreapply/callMethod passed inthisThat is, the native constructorthisUnable to bind, resulting in subclass instances not getting internal attributes.
  • Some have limitations at the bottom, such asDate, if the object’s[[Class]]notDate, throws an error



Now we use the wrapper to get the method on the built-in object by calling an instance of the new parent class:

function _wrapNativeSuper(Class) {
  // Map-based cache
  var _cache = typeof Map= = ="function" ? new Map() : undefined;
  _wrapNativeSuper = function _wrapNativeSuper(Class) {
    // protect, and return directly if there is no, or non-native function
    if (Class === null| |! _isNativeFunction(Class))return Class;
    if (typeofClass ! = ="function") {
      throw new TypeError("Super expression must either be null or a function");
    }
    if (typeof_cache ! = ="undefined") {
      if (_cache.has(Class)) return _cache.get(Class);
      _cache.set(Class, Wrapper);
    }

    function Wrapper() {
      // A new instance is generated with the parent constructor
      return _construct(Class, arguments, _getPrototypeOf(this).constructor);
    }
    // Attach the parent's prototype method
    Wrapper.prototype = Object.create(Class.prototype, {
      constructor: {
        value: Wrapper,
        enumerable: false.writable: true.configurable: true}});// Fix __proto__ pointing
    return _setPrototypeOf(Wrapper, Class);
  };
  // This returns an instance of the parent class
  return _wrapNativeSuper(Class);
}
Copy the code


summary

This article starts from the prototype chain, mainly introduces the principle of JavaScript based on prototype chain inheritance, the advantages and disadvantages of various inheritance in ES5, and the appearance of Class syntax sugar itself. I’m going to focus on the basics, and I’ll learn about design patterns, TypeScript, and polyfill