Don’t be put off by the fancy name. I’m not going to give you a history of archetypes, but this article is intended to help you understand why archetypes and prototype chains are unique language features that no other language (or programming language I’ve studied) has ever seen. This is where I was most confused when I switched from C to JavaScript.

1. Start by creating objects in JavaScript

Everyone knows that JavaScript is an object-oriented language, but there is no concept of classes (except for the current ES6 standard). Personally, I think ES6 is a new standard encapsulated on ES5, and the essential realization is ES5. Therefore, mastering ES5 can be regarded as mastering the essence. There is no concept of a class, but there is certainly a concept of an object, and objects in JS are different from other object-oriented languages such as C++. Each object is implemented based on a reference type (e.g. Array/Date/Function are reference types, see Chapter 5 of JavaScript Advanced Programming (3rd Edition)) or a custom type.

Previously the most common way to create an Object was (by creating an Object instance) :


var animal = new Object();
animal.name = 'WangWang';
animal.type = 'dog';

animal.say = function(){
  console.log('I am a ' + this.type);
}
Copy the code

Then came the creation of object literals:

var Animal= { name: 'WangWang', type: 'dog', say: function(){ console.log('I am a ' + this.type); }}Copy the code

The first thing to be clear is that an object must contain properties and methods: Name and type must be properties, and say must be methods. Second, properties have internal properties in the browser, which are used by the internal JS engine.

1.1. Talk about properties in JS objects (Property)

According to THE ES5 standard, attributes, in addition to the name we remember, a value, in the form of a key-value pair, are actually a large text inside the browser.

Attributes are divided into Data Property and Accessor Property. The name and type just defined are data attributes. The difference between data attributes and accessor data is that accessor attributes have [[Get]] and [[Set]] methods and do not contain [[value]] attributes.

The data properties contain 4 different features: [[64x], [[Enumerable]], [[Writable]], [[Value]].

The accessor property contains four different features: [[Video]], [[Enumerable]], [[Get]], [[Set]].

Although these features are for internal use in browsers, ES5 still provides interfaces to invoke:

Object.defineProperty(obj, prop, descriptor);
Object.defineProperties(obj, props);
Object.getOwnPropertyDescriptor(obj, prop);
Object.getOwnPropertyDescriptors(obj);
Copy the code

A random example (in the Chrome console) :

> Object.getOwnPropertyDescriptor(Person, "name")
> Object {value: "WangWang", writable: true, enumerable: true, configurable: true}
Copy the code

For more details on these four apis (such as compatibility), see: MDN

2, create JS object advanced

Although either Object constructors or Object literals can be used to create a single Object, there is obviously one obvious drawback: This combination of object creation and object instantiation leads to code that can’t be reused and then a lot of duplicate code, so a new approach to object creation — the factory pattern — has been developed to address this problem. This form is slowly moving closer to C++ class and object instantiation and closer to actual code development.

2.1. Factory Mode

It’s a very good name, as soon as we hear the name, we know that there is a factory and we can use the factory’s mold to create the object we want as long as we provide the raw materials (that is, the process of instantiation).

Because classes cannot be created in ES5, functions encapsulate the details of creating objects with a specific interface. Such as:

function createAnimal(name, type){
   var o = new Object();
   o.name = name;
   o.type = type;
   o.say = function(){
       console.log('I am a ' + this.type); 
   }
   return o;
}

var dog = createAnimal('WangWang', 'dog');
Copy the code

While this approach solves the problem of object instantiation code duplication, it does not solve the problem of object recognition (that is, an object created in this way cannot know its type, such as an object created in the previous second approach, which can know its type is Person). So evolution has developed another way to create objects.

2.2. Constructor pattern

A constructor is a basic concept in C++. It initializes calls after an object is instantiated and performs some copy operations. It can be regarded as an initialization function. Similarly, the JS constructors used here are not the same in form as in C++, but they are essentially the same. JS provides some native constructors such as Object/Array/String, and can also be created custom. Such as:

function Animal(name, type){
   this.name = name;
   this.type = type;
   this.say = function(){
       console.log('I am a ' + this.type); 
   }
}

var dog = new Animal('WangWang', 'dog');
Copy the code

This constructor has the following three characteristics:

  • No object is explicitly created
  • Attributes and methods are assigned directly to this object
  • No return statement

When performing the new operation, you go through the following four steps:

  • Create an object
  • Assign the constructor’s scope to the new object (so this pointer points to the new object)
  • Execute the code in the constructor
  • Return a new object

Dog is an Animal instance. In C++ tradition, each instance must have a constructor property called constructor. The same goes for JS.

The previous three methods create the same object, so take any of them and compare them with the factory mode:

You can see that the constructor method does have an extra attribute, and why these attributes are grouped under __proto__ is what we’ll talk about later.

So we can use the constructor property to identify the type of the object (such as Animal in this case), which is verified using instanceof.

The main problem with using constructors is that each method has to be created in each instance. This means that when we create an Animal object, the method inside it is actually an instance of the Function object.

this.say = new Function( console.log('I am a ' + this.type);)

This resulted in creating multiple instances that instantiated many function objects, which obviously increased memory consumption. To solve this problem, we introduced the prototype pattern.

3. Prototyping

Each function we create has a Prototype) property, which is a pointer to an object whose purpose is to contain properties and methods that can be shared by all instances of a particular type. It is not the same as __proto__ below, which is explained in section 3.1.

As we saw in Figure 1, every object created by any method has a __proto__ attribute. This __proto__ attribute is the key link to the prototype chain. In the previous section, the new operation performed four steps. The second step is the assignment (dog.__proto__ = animal.prototype), which can be seen from console print:

Prototype patterns are created using methods like this:

function Animal(){}

Animal.prototype.name = 'WangWang';
Animal.prototype.type = 'dog';
Animal.prototype.say = function(){
       console.log('I am a ' + this.type); 
   };

var dog = new Animal();
Copy the code

The difference between the stereotype pattern and the constructor pattern can be seen in the following diagram:

Constructor pattern:

Prototype mode:

From the above two pictures you can see the pros and cons of the prototype. How can it be improved? Is it possible to take advantage of both by blending them? If you think so, then you are right. This is the subject of section 3.2.

In addition to the stereotype using the assignment operations above, we currently prefer to use object literals or the new keyword to manipulate the stereotype. But there is an important point to make about using object literals or new keywords: When you use object literals or new keywords, you create a new object to replace the original prototype object.

Very abstract, right? Here’s a picture to tell you the truth:

Code:

function Animal(){}

Animal.prototype = {
     name: 'WangWang',
     type:  'dog',
     say : function(){
       console.log('I am a ' + this.type); 
   }
}
var dog = new Animal();
Copy the code

Or:

function Species(){
  this.name =  'WangWang';
  this.type =  'dog';
  this.say = function(){
       console.log('I am a ' + this.type); 
   };
}

function Animal(){}

Animal.prototype = new Species();
var dog = new Animal();
Copy the code

The corresponding prototype mode diagram of the two:

So because of this overridden effect, when using this method be sure to note that the sample remains the same as the original sample.

Both have their advantages. How about a combination? What does the prototype chain have to do with the prototype?

Look at the next article: JavaScript Archetypes and The Past life of prototype Chains (II)


Did this article help you? Welcome to join the front End learning Group wechat group: