Writing in the front

I was a little confused when I saw a question about “how to create a pure object”. Isn’t everything an object in JavaScript? Any type can inherit from Object, that is, have methods on Object constructor prototypes. Why is there such a thing as a pure object? The Object. Create (NULL) method initializes an Object without a prototype. Then there’s the question, “What’s the end of the prototype?” Boy, Ralph Drew? It was null. But we can see a strange phenomenon:

console.log(typeof null= = ='object') // true
console.log(Object.prototype.__proto__ === null) // true
Copy the code

Why is null of type an object and the end of the object prototype chain null? This is a bug in the JavaScript language. In the 32-bit system used in early versions of JavaScript, the type information of a low-stored variable was used, with the type label 0 representing the object. Null, on the other hand, is a null pointer, starting with 000, so null’s type label is also mistakenly defined as 0 and is therefore recognized as an object. Since then, NULL took office, like zhang Youren in the list. In short, my nominal son is actually my grandfather… I’m interested in JavaScript prototype chains because of all the fun stuff, so let’s get started

Prototype chain

When it comes to prototype chains, we have to think about two things:

  • Why have a prototype chain?
  • Is there a prototype chain regardless of type?

Where did the prototype chain come from?

As we all know, JavaScript was originally designed only to validate browser forms, so the founders didn’t design a complex class to inherit this feature, but simply implemented it by borrowing the new operator from C++ and Java. The problem is that if the member variables of each instance are private, the data is too independent to be shared, and each instance needs to create new members in the constructor, which is a huge waste of memory. Prototype was designed to place members that need to be shared in Prototype and private variables in constructors. However, you can’t have a downward index without upward index. Constructor appears. That is, instances that want to find shared members need to look in constructor prototype. So, how do I get this shared member? The _proto_ is thus designed. The prototype chain was born

myth

Does the instance have a constructor attribute? It is common to see instances finding their own constructors via the constructor attribute. This statement is true, but we can see by looking at the specific properties of the instance object that it does not have a property called constructor. Constructor === Ctro In fact, this step is done via the _proto_ bridge. Constructor is essentially instance.__proto__. Constructor, since the _proto_ of the instance points to the constructor’s prototype, and the prototype object has the constructor attribute.

1

function Person() {}
Person.prototype.say = function() {}
const me = new Person()
const you = new Person()
console.log(me.constructor === Person) // true -> both point to Person
console.log(you.constructor === Person) // true -> both point to Person
console.log(me.constructor.prototype === me.__proto__) // true -> both point to the prototype of Person
console.log(me.__proto__.constructor === Person) // true -> both point to Person
console.log(me.__proto__.constructor === you.constructor) // true -> both point to Person
console.log(me.constructor.prototype === you.constructor.prototype) // true -> both point to the prototype of Person
console.log(me.__proto__ === you.__proto__) // true -> both point to the prototype of Person
console.log(me.__proto__.say === you.__proto__.say) // true -> both refer to the say method on the prototype of Person
Copy the code

Is there a prototype chain regardless of type?

The problem goes to the underlying philosophy of JavaScript, which was originally designed to define all types as objects. Since any type is an Object, it must be an instance of Object. Don’t believe it? So you define any primitive type variable and see if it has the _proto_ attribute ~

digression

_proto_ is a canonical property, but the specification has been removed from the Web standard, and although it can still be called in many browsers today, for security reasons, Object.getprototypeof (instance) or reflect.getprototypeof (instance) are recommended instead of ~

2

function Person(hobbies) {
  this.hobbies = hobbies
}
Person.prototype.getHobbies = function() {
  return this.hobbies
}
const somebodyA = new Person(['movie'.'football'])
const somebodyB = new Person(['CSGO'])
console.log(somebodyA.hobbies === somebodyB.hobbies) // false -> one is [' movie ', 'football '] one is ['CSGO']
console.log(somebodyA.getHobbies() === somebodyB.getHobbies()) // false -> one is [' movie ', 'football '] one is ['CSGO']
console.log(somebodyA.getHobbies === somebodyB.getHobbies) / / true - > all point to the Person. The prototype. GetHobbies method
console.log(somebodyA.prototype === somebodyB.prototype) // true -> both point to Person.prototype
Copy the code

(1)

From the above we can see that the way an instance gets a shared member is by using _proto_ to get the member from the constructor prototype. What if the member does not exist in the constructor? Will always be the prototype of the prototype (prototype of the prototype of the prototype…) Search, until found can not find, return undefined

Object.prototype.name = 'aeorus'
function Person() {}
const me = new Person()
console.log(me.name) // aeorus
Copy the code

3. (2)

function Person() {}
Person.prototype.name = 'aiolos'
const me = new Person()
console.log(me.name) // aiolos
delete Person.prototype.name
console.log(me.name) // undefined
Copy the code

(3)

Object.prototype.name = 'aeorus'
function Person() {}
Person.prototype.name = 'aiolos'
const me = new Person()
console.log(me.name) // aiolos
delete Person.prototype.name
console.log(me.name) // aeorus
Copy the code

New operator

I mentioned above that the instance itself does not have the constructor property, only the prototype of the constructor. For the instance itself, all you can do is get the prototype of the constructor via _proto_. So what exactly does the new operator do when it instantiates a constructor?

Set prototype chain

Briefly, it can be divided into the following four chains:

Constructor -- constructor -- constructor -- constructor -- constructor -- constructor -- constructor -- constructor -- constructor -- constructor -- constructor -- constructor \ __proto__ constructor) - > instance constructor - \ __proto__ - > prototypeCopy the code

Emulate the inner workings of the new operator

  • A constructor can have a return value that is ignored if it is a primitive type or overridden if it is a reference type
  • throughObject.create(Ctro.prototype)Generates a new object that inherits the Ctro prototype
  • Determine the return value type of the constructor call
  • Returns the instance based on the return value type
function Person(name) {
  this.name = name
  // return 1
  // return [1, 2]
  // return { a: 1 }
  // return null
  /* return () => { console.log('a') } */
}
Person.prototype.say = function() {
  return this.name
}
function _createInstance() {
  const Ctro = Array.from(arguments) [0]
  if(! Ctro ||typeofCtro ! = ='function') {
    throw 'createInstance must receive a function for constructor'
  }
  // Get the constructor's input parameter
  const args = [].slice.call(arguments.1)
  // Get the new object with the stereotype of the constructor
  const newObject = Object.create(Ctro.prototype)
  // Call the constructor to point its this to the new object for two purposes
  // One is to bind the members of the constructor to the new object
  // The return value is a reference type
  const ctroReturnResult = Ctro.apply(newObject, args)
  // Check whether ctroReturnResult has a return value and its return value type
  const isObject = typeof ctroReturnResult === 'object'&& ctroReturnResult ! = =null
  const isFunction = typeof ctroReturnResult === 'function'
  // ctroReturnResult is returned if a non-null object or method returns a value
  if (isObject || isFunction) return ctroReturnResult
  // If not, newObject is returned
  return newObject
}
const me = _createInstance(Person, 'aeorus')
Copy the code

inheritance

JavaScript is not designed with inheritance in mind, so there are a lot of clever inheritance methods. I’m not going to go into too many inheritance methods, but I’ll just take out the three most classic inheritance methods and the new class inheritance method in ES6

function Person(name, age) {
  this.name = name
  this.age = age
  this.reference = ['black'.'white'.'gray']
}
Person.prototype.getInfo = function() {
  return `My name is The ${this.name}, and I'm The ${this.age} years.`
}
Copy the code

Prototype chain inheritance

  • Both an instance of a parent class and an instance of a subclass
  • The parent class members are so shared that a member that changes all instances of the parent class will change
  • A subclass cannot pass arguments when instantiating its parent class as its prototype
  • A subclass cannot define properties on its own stereotype before instantiating its parent class as its stereotype
  • A subclass can inherit only one parent class
function Male(name, age) {
  this.name = name
  this.age = age
}
Male.prototype = new Person()
Male.prototype.getAge = function() {
  return this.age
}
// test
const me = new Male('aeorus'.28)
const you = new Male('aiolos'.30)
console.log(me instanceof Person) // true 
console.log(me instanceof Male) // true 
me.getInfo() // My name is aeorus, and I'm 28 years. -> My name is undefined, and I'm 28 years old
me.reference.pop()
console.log(me.reference) // ["black", "white"] -> Bad
console.log(you.reference) // ["black", "white"] -> Bad
Copy the code

Combination of inheritance

  • Everything is fine. It’s justPerson.callOnce, and then againnew PersonOnce, called twice
function Male(name, age) {
  Person.call(this, name, age)
  this.name = name
  this.age = age
}
Male.prototype = new Person()
Male.prototype.constructor = Male
// test
const me = new Male('aeorus'.28)
const you = new Male('aiolos'.30)
console.log(me instanceof Person) // true 
console.log(me instanceof Male) // true 
me.getInfo() // My name is aeorus, and I'm 28 years. -> My name is undefined, and I'm 28 years old
me.reference.pop()
console.log(me.reference) // ["black", "white"]
console.log(you.reference) // ["black", "white", "gray"]
Copy the code

Parasitic combinatorial inheritance

function Male(name, age) {
  Person.call(this, name, age)
  this.name = name
  this.age = age
}
const prototype = Object.create(Person.prototype)
prototype.constructor = Male
Male.prototype = prototype
// test
const me = new Male('aeorus'.28)
const you = new Male('aiolos'.30)
console.log(me instanceof Person) // true 
console.log(me instanceof Male) // true 
me.getInfo() // My name is aeorus, and I'm 28 years.
me.reference.pop()
console.log(me.reference) // ["black", "white"]
console.log(you.reference) // ["black", "white", "gray"]
Copy the code

ES6 class inheritance

class Male extends Person {
  constructor(name, age) {
    super(name, age)
  }
  getAge() {
    return this.age
  }
  getInfo() {
    return super.getInfo() + ' And this message is in class Male'}}// test
const me = new Male('aeorus'.28)
const you = new Male('aiolos'.30)
console.log(me instanceof Person) // true 
console.log(me instanceof Male) // true 
me.getInfo() // My name is aeorus, and I'm 28 years. And this message is in class Male
me.reference.pop()
console.log(me.reference) // ["black", "white"]
console.log(you.reference) // ["black", "white", "gray"]
Copy the code

Write a class

Now that we’re talking about ES6 class inheritance, how do we implement this built-in class

class Parent {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  getInfo() {
    return `My name is The ${this.name}, and I'm The ${this.age} years.`
  }
  getName() {
    return this.name
  }
  static getPrototype() {
    return "Class constructor Parent cannot be invoked without 'new'"}}Copy the code
function _classCallCheck(instance, Constructor) {
  if(! (instanceinstanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function")}}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)
  }
}
function _createClass(Ctro, props, staticProps) {
  if (props) _defineProperties(Ctro.prototype, props)
  if (staticProps) _defineProperties(Ctro, staticProps)
  return Ctro
}
var Parent = function () {
  function Parent(name, age) {
    _classCallCheck(this, Parent)
    this.name = name
    this.age = age
  }
  _createClass(Parent, [{
    key: "getInfo".value: function getInfo() {
      return "My name is ".concat(this.name, ", and I'm ").concat(this.age, " years.")}}, {key: "getName".value: function getName() {
      return this.name
    }
  }], [{
    key: "getPrototype".value: function getPrototype() {
      return "Class constructor Parent cannot be invoked without 'new'"}}])return Parent
}()
Copy the code

Object.create

I started with a question — how do you create a pure object? We now know that this is achieved through the Object.create(NULL) method. So, how does this approach work? What does it have to do with the archetype that we’re talking about here? At this point, let’s explore the various native methods for manipulating prototypes. MDN defines this method as: The _proto_ for creating a new object, using an existing object to provide the newly created object, is a bit of a mouthful, but we can see the _proto_ word, indicating that there is a constructor hidden inside. Pass in an object and return an instance of that object. Is that an explanation that will soon lead to a handwritten one?

const _objectCreate = function(prototype) {
  function F() {}
  F.prototype = prototype
  return new F()
}
const obj = _objectCreate({ name: 'aeorus' })
console.log(obj)
Copy the code

But don’t forget that Object.create has a second optional parameter, propertyObject, which is a property configuration Object (see the third parameter of Object.defineProperty). The properties defined in this parameter will be bound directly to the new Object. Therefore, we also need to iterate over the second parameter and mount it to the new Object via Object.defineProperty.

const _objectCreate = function(prototype, propertyObject) {
  function F() {}
  F.prototype = prototype
  const result = new F()
  for (let key in propertyObject) Object.defineProperty(result, key, propertyObject[key])
  return result
}
const obj = _objectCreate({ name: 'aeorus' }, {
  // Foo becomes the data attribute of the created object
  foo: {
    writable:true.configurable:true.value: "hello"
  },
  // bar becomes the accessor property of the created object
  bar: {
    configurable: false.get: function() { return 10 },
    set: function(value) {
      console.log("Setting `o.bar` to", value); }}})console.log(obj)
Copy the code