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
- through
Object.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 just
Person.call
Once, and then againnew Person
Once, 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