I’m sure many of you have read a lot of articles about prototypes on various platforms and in books. Now, I’m going to take you through a new perspective on prototyping.

As you already know, archetypes are the foundation for implementing JavaScript object-oriented systems. However, you should also know that the Prototype pattern is actually a design pattern, but also a programming paradigm.

Understand the prototyping paradigm

Many of you may be a little confused by this: SINCE I started using JavaScript, I’ve really been working with prototypes, and by the way, I’ve been working with the prototyping paradigm. But this paradigm is confusing to me — do I have any choice but to prototype?

As JavaScript developers, we really don’t have a choice — prototypes are fundamental to JavaScript’s object-oriented system. But in other languages, such as JAVA, classes are at the heart of their object-oriented systems.

A class is an abstraction of the structure and behavior of a class of entities. In class-based object-oriented languages, we focus first on abstraction — we need to design a generic class before we can instantiate an object with that class, and then focus on the concrete level.

In prototypical languages like JS, the first thing we need to focus on is concreteness — the behavior of each instance of concreteness. Based on the behavior characteristics of different instances, we associate similar instances with a prototype object — the associated prototype object contains the more common behaviors and properties. Instances based on this prototype can “copy” its capabilities.

Yes, in the prototyping paradigm, we create new objects by copying. But this “copy” doesn’t have to be creating new memory and re-implementing the prototype object — we’re copying capabilities, not entities. In JS, for example, we do “copy” by making the new object keep a reference to the original object.

“Classes” in JavaScript

At this time have a part small partner estimate to fry hair: what?? JavaScript only uses Prototype? I see you are still alive in the last century, ES6 already support classes! Now JavaScript is also a class-centric language.

This is dangerous because ES6 classes are prototyped syntactic sugar:

The JavaScript classes introduced in ECMAScript 2015 are essentially syntactic sugar for JavaScript’s existing prototype-based inheritance. Class syntax does not introduce a new object-oriented inheritance model to JavaScript.

When we try to define a Dog class using class:

class Dog {
  constructor(name ,age) {
   this.name = name
   this.age = age
  }
  
  eat() {
    console.log('The meat bones are delicious.')}}Copy the code

This is exactly equivalent to writing a constructor like this:

function Dog(name, age) {
  this.name = name
  this.age = age
}

Dog.prototype.eat = function() {
  console.log('The meat bones are delicious.')}Copy the code

So the nature of JS’s prototype as the cornerstone of its object-oriented system has not changed.

Understand prototypes and prototype chains

The core idea of the prototype programming paradigm is to use instances to describe objects and use instances as the basis for defining objects and inheritance. In JavaScript, the prototype programming paradigm is represented by inheritance based on prototype chains. Among them, the understanding of prototype and prototype chain is the key.

The prototype

In JavaScript, each constructor has a Prototype attribute, which points to the constructor’s prototype object, which has a Construtor attribute pointing back to the constructor; Each instance has a __proto__ attribute, and when we use the constructor to create an instance, the instance’s __proto__ attribute points to the constructor’s prototype object.

Specifically, when we create an object using constructors like this:

// Create a Dog constructor
function Dog(name, age) {
  this.name = name
  this.age = age
}
Dog.prototype.eat = function() {
  console.log('The meat bones are delicious.')}// Use the Dog constructor to create an instance of Dog
const dog = new Dog('Little Feifei'.3)
Copy the code

Several entities in this code have this relationship:

Prototype chain

Now I make two method calls based on the above code:

dog.eat() // "Meat bones are delicious"


dog.toString() // [object Object]
Copy the code

The eat and toString methods were successfully called even though they were not defined manually in the dog instance. This is because when I try to access the properties/methods of a JavaScript instance, it first searches for the instance itself; When it finds that the instance does not have a corresponding property/method defined, it searches for the instance’s prototype object instead. If it can’t find any of the prototype objects, it searches for the prototype object of the prototype object, and the trajectory of the search is called the prototype chain.

Take the call to our eat and toString methods as an example. Their search looks like this:

These connected prototypes form the so-called “prototype chain”.

Note: Almost all objects in JavaScript are instances of Object at the top of the prototype chain, except Object.prototype (of course, if we manually create an Object with no prototype at all by calling Object.create(null), It is not an instance of Object.

Prototype and object oriented real problem analysis

Prototype side knowledge occasionally see a single proposition, but more is combined with other JS core knowledge proposition. In this way, the distinction of the topic can be raised as a whole, and at the same time, the candidate’s basic skills can be comprehensively investigated.

To deal with such a topic, we should first keep a cool head, screen out the knowledge points involved in the topic, classification, fast mapping in my mind; Most of the questions seem very complex at first glance, but if you want to retreat because of the complexity, you are in for the trick! Most of the time, as long as you can calm down to catch the prototype chain, you will find that their fear of difficulty is less than half, the whole context of the answer is also clear.

Proposition 1: prototype foundation + constructor foundation

var A = function() {};
A.prototype.n = 1;
var b = new A();
A.prototype = {
  n: 2.m: 3
}
var c = new A();

console.log(b.n);
console.log(b.m);

console.log(c.n);
console.log(c.m);
Copy the code

Easy to misdial:

Here’s what I know a lot of you might say:

2
3
2
3
Copy the code

But if you drop it into the console, you’ll find the answer:

Why is that? Let’s look at the relationships between the objects in this example

Step1: define archetypal relationships:

The relationship between instance B and A:

The relationship between instance C and A:

Step2: key idea analysis – the working mechanism of the constructor

I believe many students have no doubt about c instance, but more doubt about why B instance and C instance inherit from the same prototype, but have different performance.

One thing to note here: what does new do when we create an instance with new? It does these four things:

  1. Create a memory space for the new object
  2. Refer this inside the function to the memory space created by 1
  3. Associate the instance with the prototype object by pointing the _proto_ attribute of the new object to the prototype attribute of the corresponding constructor
  4. Execute the logic inside the function, and eventually the constructor will return the new object for you, even if you don’t return it manually

Note step 2: After step 2, the prototype of the instance object saves a reference to the constructor’s prototype. What is the constructor’s prototype at the time the B instance is created? This is the object:

So n of instance B is 1; And since it has no m property, it prints undefined.

Some students will say that, but we also made some changes to the prototype of A. B If I save the reference, it should sense my change ah! Note how you modify A’s prototype form:

A.prototype = {
  n: 2.m: 3
}
Copy the code

This is not technically a modification, but a reassignment operation. The essence of this action is to point A’s prototype to A new js object:

From the figure, we can see that A unilaterally cut off the relationship with the old Prototype, while B still keeps the reference of the old prototype. This is what causes the difference in performance between instance B and instance C.

Proposition 2: own attribute and archetypal inheritance attribute

function A() {
    this.name = 'a'
    this.color = ['green'.'yellow']}function B() {}

 B.prototype = new A()
 var b1 = new B()
 var b2 = new B()
 
 b1.name = 'change'
 b1.color.push('black')

console.log(b1.name) // 'change'
console.log(b1.color) // ["green", "yellow", "black"]
console.log(b2.name) // 'a'
console.log(b2.color) // ["green", "yellow", "black"]
Copy the code

Step1: draw a prototype chain diagram

Instead of using A to create A new object, you use A “middleman” B, which complicates the prototype chain. But it’s not a problem. Let’s draw it:

Step2: the difference between read operation and write operation

Now, one of the differences you see between B1 and B2 is that B1 has its own name property. This might confuse some of you, but this line of code:

b1.name = 'change'
Copy the code

When looking for the name attribute of B1, shouldn’t we look along the prototype chain and then locate and modify the name on the prototype chain?

In fact, this “upstream” variable positioning occurs if and only if we are doing a “read” operation.

So this is an assignment, this is a write operation. If the name attribute does not already exist on B1 while writing the attribute, the new attribute will be created for B1 without disturbing the prototype chain.

The color property also looks like a “write” operation. Why doesn’t it add a new property to B2 instead of changing the color on the prototype chain? First, write it like this:

b1.color.push('black')
Copy the code

This includes writing (modifying the value of an object’s property) :

b1.color.attribute = 'xxx'
Copy the code

It does not actually change the reference to the object, but merely modifies its contents from the original object. An operation like this, which does not trigger a reference to change, follows a prototype chain query + modify process rather than a process of creating new properties in place. How do I make it a write operation? Direct assignment:

b1.color = ['newColor']
Copy the code

In this way, color becomes a property of b1. Because [‘ newColor ‘] is a brand new array, it corresponds to a brand new reference. To JS, this is really “writing” a new property to B1.

Easy wrong inspiration.

Some students may look down on this test method: don’t pay attention to the type of reference? Too normal?

In fact, the above problem is very discriminative in practice. A lot of candidates are probably like you right now, thinking “this seems basic,” and jumping in. I want to remind you that even a normal problem requires your comprehensive ability to fix it. This “comprehensive ability” not only means that you have to synthesize the knowledge points you know, but also includes how careful and cautious you are to do the questions — remember, there are no difficult problems in the interview, difficult in the heart.

Proposition 3: comprehensive investigation of constructors

function A() {}
function B(a) {
    this.a = a;
}
function C(a) {
    if (a) {
        this.a = a;
    }
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;

console.log(new A().a); / / 1
console.log(new B().a); // undefined
console.log(new C(2).a); / / 2
Copy the code

Step1 draw the prototype chain diagram

Step2 construct the working mechanism of the function

Combining our previous analysis of constructors, when we create a new object using the new + constructor like this:

function C(a) {
    if (a) {
        this.a = a; }}var c = new C(2)
Copy the code

Four things actually happened:

  1. Carve out a memory space for the C instance
  2. Refer this inside the function to the memory space created by 1
  3. Point the _proto_ attribute of instance C to the prototype attribute of constructor C
  4. Execute the logic inside the function, and the constructor will return the c instance for you

Let’s look at the three console calls based on this conclusion:

  • new A ().aThe constructor logic is empty and the instance object _proto_ returned contains a = 1 property. When new A ().a, it finds that the instance object itself does not have A, so it finds A in the prototype along the prototype chain and outputs its value as 1.
  • new B ().aThe: constructor creates a property of its own for the instance object unconditionally. The value of this property is determined by the input parameter. Here our input parameter is undefined, so a is undefined.
  • new C (2).aThe constructor will conditionally create a property of its own for the instance object — if there is an input parameter a that Boolean determines is not false, then create the corresponding value of a for the instance object. Otherwise, don’t do anything. Here we pass in a 2, so the example outputs a value of 2.

summary

The prototype interview questions look intimidating at first glance, but when broken down, they’re very simple. In real interviews, tricky prototype questions are less common and more about your understanding of the most basic principles, so it is not recommended to spend a lot of time drilling difficult questions. Of particular interest here is the understanding and use of constructors. When you do a problem, the key to getting it right is to be able to identify the prototype relationships involved in the problem. The challenge in defining archetypal relationships is patience and carefulness. Do the prototype questions can not be fast, for the confirmed prototype relationship, should be repeatedly combed, two or even three times after confirmation of the answer.