Analysis of Common Ways and Principles of JS Object Creation

Keywords: Javascript Attribute Programming ECMAScript

==== This article was written earlier and belongs to the article migration @2017.06.27.====

Preface

As the saying goes, "In the js language, everything is an object", and there are many ways to create objects, so today we will do a comb.

The simplest way

The easiest way to create objects in JavaScript is to use object literals or Object constructors

Object literal form

1 var person = new Object();
2   person.name = "jack";
3   person.sayName = function () {
4   alert(this.name)
5 }

Using Object constructor

1 var person = {
2   name: "jack";
3   sayName: function () {
4     alert(this.name)
5   }
6 }

Obvious drawbacks: When multiple objects are created, code duplication occurs, so the factory pattern comes into being.

Factory model

To understand the factory model in a more general way, factory: "I create an object, and the process of creating it is entirely my responsibility, but when the task is completed, I have nothing to do with it, O() O() O() O) O).

 1 function createPerson (name) {
 2   var o = new Object();
 3   o.name = name;
 4   o.sayName = function () {
 5     alert(this.name)
 6   }
 7   return o
 8 }
 9 
10 var p1 = new createPerson("jack");

Obvious drawbacks: All object instances are `Object'types, and almost all types can be distinguished. You said that if you can't distinguish between types, you can't distinguish between them. I don't believe it! Let's look at the code.

1 var p1 = new createPerson("jack");
2 var p2 = new createPerson("lucy");
3 
4 console.log(p1 instanceof Object); //true
5 console.log(p2 instanceof Object); //true

You see, is that the reason? So in order to solve this problem, we use the constructor pattern?

Constructor Pattern

The constructor pattern is the function where I just create instances of one type of object, and I don't care about the others (notice, there's a concept of type here, it feels like a small group).

 1 function Person (name) {
 2   this.name = name;
 3   this.sayName = function () {
 4     alert(this.name)
 5   }
 6 }
 7 
 8 function Animal (name) {
 9   this.name = name;
10   this.sayName = function () {
11     alert(this.name)
12   }
13 }
14 
15 var p1 = new Person("jack")
16 p1.sayName() //"jack"
17 
18 var a1 = new Animal("doudou")
19 a1.sayName() //"doudou"
20 
21 console.log(p1 instanceof Person) //true
22 console.log(a1 instanceof Animal) //true
23 console.log(p1 instanceof Animal) //false(p1 Obviously not. Animal Type, so yes false)
24 console.log(a1 instanceof Person) //false(a1 Obviously not. Person Type, so it's the same false)

The above code demonstrates that the constructor pattern does distinguish between object types. So is the pattern perfect, but it's not. Let's look at the following code together:

1 //Next, the code above
2 console.log(p1.sayName === a1.sayName) //false

Have you found any problems? ` What does it mean that `sayName'of p1` is not the same as `sayName' of `a1'? We can understand that there is no concept of "public" in "constructor pattern". Every object instance created has its own set of attributes and methods,'attributes are private'. But you have to do a set of methods yourself, which is not necessary.
Obvious drawbacks: As described above, in order to solve this problem, a new model `prototype model'has emerged. This model is a leap in stages. Let's look at the `prototype model'.

Prototype pattern

Here's a word to remember: the attributes and methods in constructors are not shared among each object instance, but each of them has its own set. To achieve sharing, the attributes and methods must be stored in the prototype of constructors. What does this sentence mean? Let's explain in detail below.
When a constructor is created (as is the case with ordinary functions), a prototype is automatically generated. The relationship between the constructor and the prototype is one-to-one, and there is only one `constructor'attribute in the `prototype' (where is there, there is clearly another `proto proto_', which we will not discuss here, but will explain later).

What is this `constructor'? It is a pointer-like reference to the `prototype'constructor, and the pointer must exist by default.

1 console.log(Person.prototype.constructor === Person) //true

As I said just now, `prototype'is `automatic generation'. In fact, there is another manual way to generate `prototype':

1 function Person (name) {
2   this.name = name
3 }
4 Person.prototype = {
5   //constructor: Person,
6   age: 30
7 }
8 console.log(Person.prototype) //Object {age: 30}
9 console.log(Person.prototype.constructor === Person) //false

Tips: To prove that you can actually create a `prototype'manually for the constructor, you add a `name' attribute to `prototype'.
Maybe you've noticed a problem with this line of code:

1 console.log(Person.prototype.constructor === Person) //false

Why is the result `false'? Brother, the `prototype'was generated by default, and then we used another way: manual settings. Specific analysis of the principle of manual settings:
1. The `prototype'of the constructor is also an object.

2. When we set `prototype'in this way, we actually cut off the original `Person.prototype', and then re-refer to another object.

3. At this point, the constructor can find `prototype', but `prototype'can't find the constructor.

1 Person.prototype = {
2   //constructor: Person, // Because of the constructor attribute, I didn't declare it. prototype uses it to find the constructor. You forgot to declare it.
3   age: 30
4 }

4. So, to show the prototype of the constructor that is set manually without losing the connection between them, we need to do this:

1 function Person (name) {
2   this.name = name
3 }
4 Person.prototype = {
5   constructor: Person, //constructor Don't forget!!
6   age: 30
7 }

Voice-over: "At this point, you haven't talked about how prototype patterns can share attributes and methods." Don't worry, start right away.

What is the relationship between object instance - constructor - prototype?

Do you see what this picture means?
1. When an object instance accesses an attribute (the method still exists), if it does not have the attribute itself, it will search on the `prototype'of the constructor through the `proto_____________ chain.
2. Constructors and prototypes are one-to-one and object instances are one-to-many relationships, but not every creation of an object instance generates a corresponding `prototype'.`
This is the core of the prototype pattern. The conclusion is that declaring attributes or methods on the prototype allows them to be shared between object instances.

And then the prototype model is perfect? No, it has the following two main problems:
Question 1: If an object instance has a property or method with a duplicate name on the prototype, then when accessing the property or method, the instance will mask the property or method on the prototype.

1 function Person (name) {
2   this.name = name
3 }
4 Person.prototype = {
5   constructor: Person,
6   name: 'lucy'
7 }
8 var p1 = new Person('jack');
9 console.log(p1.name); //jack

Question 2: Since the attributes and methods on the prototype are shared among instances, other instances will be affected when one of the object instances modifies the attributes on the prototype (base value, non-reference type value or method).


The reason is that when the basic value attribute of the instance itself and the duplicate name on the prototype are used, the instance will create the attribute and keep it for future use, while the attributes on the prototype will not be modified; but if the attribute refers to the type value, such as `Array', `Object', when the duplicate name occurs, the instance will not copy a new duplicate for its own use, or insist on sharing between the instances. This will happen in the picture above.

The above two problems are the obvious shortcomings of prototype pattern. In order to overcome these shortcomings, we usually adopt a combination pattern "Combining constructor pattern and prototype pattern". In fact, in the section of prototype pattern, this pattern has already been applied.

Combining constructor pattern with prototype pattern

This pattern is the advantage of the set constructor pattern and the prototype pattern. The constructor pattern is used to define the attributes or methods of the object instance, and the shared attributes or methods are handed over to the prototype pattern.

 1 function Person (name) {
 2   this.name = name //The attributes of the instance are declared in the constructor
 3 }
 4 
 5 Person.prototype = {
 6   constructor: Person,
 7   sayName: function () { //Shared methods exist in the prototype
 8     alert(this.name)
 9   }
10 }

Note: This pattern is currently the most widely used and highly recognized method of creating custom types in ECMAScript.

-----------------

The following modes are for different scenarios, not to say what are the shortcomings of `combining constructor mode and prototype mode', but to make up for them, not so.

Dynamic prototype model

Features: Shared methods are detected and declared in constructors, and prototypes are not displayed and created.

 1 function Person (name) {
 2   this.name = name;
 3   if (typeof this.sayName !== 'function') { //Check if the method exists
 4     console.log('sayName Method does not exist')
 5     Person.prototype.sayName = function () {
 6       alert(this.name)
 7     }
 8   } else {
 9     console.log('sayName Method already exists')
10   }
11 }
12 
13 var p1 = new Person('jack'); //'sayName Method does not exist'
14 p1.sayName(); //because sayName No, let's create it, so output here'jack'
15 var p2 = new Person('lucy'); //'sayName Method already exists'
16 p2.sayName(); //Then sayName Existing, so output'lucy'

When the `Person'constructor is called for the first time, the `sayName' method is added to `Person.prototype'; Javascript Advanced Programming says that when using dynamic prototype patterns, you cannot override prototypes using object literals. Let's understand:

 

Analysis:
1.`p1'instance creation, when the prototype does not have a `sayName' method, we will add a prototype.
2. Subsequently, we rewrote the prototype in literal form, when the old prototype was not destroyed, and it remained in contact with `p1'.
3. Later examples, i.e. `p2'here, are all associated with the new prototype; so `p1' and `p2'have their own prototypes of constructors, even though their constructors are the same.

So remember: when we adopt dynamic prototyping, don't rewrite the prototype in literal form.

Parasitic constructor pattern

Before we understand this pattern, let's first think about a question: Why do constructors use the `new'keyword to call? Code Speech:

What do we find? If the constructor is not called by the `new'method, then the explicit `return' is required, otherwise the constructor will not have a return value; but if the `new'method is used, then there is no problem.

Let's look at the parasitic constructor pattern again:

 1 function Person (name) {
 2   var o = new Object();
 3   o.name = name;
 4   o.sayName = function () {
 5     alert(this.name)
 6   };
 7   return o
 8 }
 9 
10 var p1 = new Person('jack'); //The only difference from the factory model is the use of new call
11 p1.sayName(); //jack

In fact, it doesn't matter whether new or not, because we have explicitly return o.

So what are the application scenarios of the parasitic constructor pattern? According to Advanced Javaascript Programming, for example, if we want to create a special array with additional methods, we can do this:

 1 function SpecialArray () {
 2   var values = new Array();
 3   Array.prototype.push.apply(values,arguments);
 4     values.toPipedString = function () {
 5     return this.join('|')
 6   }
 7   return values
 8 }
 9 
10 var colors = new SpecialArray('red','blue','green');
11 alert(colors.toPipedString()) //'red|blue|green'

Last but not least, the pattern and constructor are not related to the prototype, that is, they cannot distinguish the type of instance, because the constructor of the pattern is Object, and the prototype is Object.prototype.

Steady constructor model

Compared with the parasitic constructor, this model has two main differences:
1. The method of creating an object instance does not refer to this
2. Call the constructor without using the new operator
According to the requirement of the robust constructor, the previous Person constructor can be rewritten as follows:

1 function Person (name) {
2   var o = new Object();
3   o.sayName = function () {
4     alert(name) //This actually involves the knowledge of closures, which leads to the concept of private attributes.
5   }
6   return o
7 }

This pattern is best suited in some secure environments (where this and new are prohibited), and similarly, it is not related to constructors or prototypes.

epilogue

This is the summary of the way to create objects in js. I hope it will be helpful to you all.

Posted by kooza on Tue, 18 Jun 2019 12:55:29 -0700