Last time we tried to deal with the following things:
- 1. Despite the conventional wisdom, “everything in JS is an object” is not so, we found out that out of 6 data types available to the programmer, as many as 5 are primitives and only one represents the type of objects.
- 2. About objects, we learned that this is a data structure that contains key-value pairs. The value can be any of the data types (and it will be a property of the object) or a function (and it will be a method of the object).
- 3. But the primitives are not objects. Although it is possible to work with them as with an object (and this causes a delusion that the primitive is an object), but ...
- 4. Variables can be declared both by simple (literally) (var a = 'str'), and through the constructor function (wrapper) (var a = new String ('str')). In the second case, we get no longer a primitive, but an object created by the String () constructor. (what kind of magic operator is new and what is a constructor function we will find out further).
- 5. We learned that it is through the creation of a wrapper on a primitive (new String ('str')) that you can work with as an object. It is this wrapper that the interpreter creates around the primitive when we try to work with it as an object, but after performing the operation it collapses (therefore, the primitive can never remember the property that we assign to it a.test = 'test'- the test property will disappear with the wrapper ).
- 6. We learned that objects have a toString () method that returns a string representation of the object (for the type number, valueOf () will return a numeric value).
- 7. They understood that when performing concatenation operations or mathematical operations, primitives can redefine their type into the necessary one. To do this, they use the wrapper function of their types, but without the operator new (str = String (str)). (What is the difference and how it works, let's talk further)
- 8. And finally, we learned that typeof takes values from a hard-coded table (this is where another misconception comes from, based on typeof null // object).
Today I would like to talk about working with constructor functions and the prototype property, as well as about working with the objects generated by them and their __proto__ property. This is often necessary when trying to organize inheritance in JS and for understanding some of the processes occurring in it. It will also provide insight into some part of the prototype inheritance. As an example for parsing (as long as it may not be clear to everyone), let's take the following code:
Example1:
function A() {} A.prototype.x = 10; a = new A(); console.log(ax);
')
Constructor functions, like prototype objects.
Let's start in order. Consider a seemingly simple line of code.
function A() {}
The very first thing you can say: "We declared a function named A". Exactly. But there are nuances.
1. Do not forget that in JS - almost everything is an Object. The function, as it turned out, is no exception (it is even two objects related by reference).
2. It can be used as a constructor function.
In JavaScript, there is no what is called classes. The work of classes in JavaScript is performed by constructor functions that create objects with certain specified properties.
In general, any function object in JS can be a constructor (I'm talking about user-defined functions). They can be conditionally divided into three (DF (Function Declaration), FE (Function expression), functions created by the Function () constructor). All these functions have their own characteristics (for this reason they are divided into different groups), but I will not talk about them here, if anyone is interested, I will answer personally or write separately about them another time. However, they have one common feature that allows them to be constructors - the presence of the internal [[Construct]] and [[Call]] properties, as well as the explicit prototype property (about it below).
It is the internal [[Construct]] method that is responsible for allocating memory for a new object and initializing it. However, this does not mean that a function call will lead to the creation of an object, of course not. To do this, before calling the function you need to put the operator new. It is new that runs the [[Construct]] method and its associated processes.
function A(){} A();
3. You can also say that this is a declaration function (DF) and so on, but the rest is not important yet.
So Function "A" (from the first line of the first example) is a constructor function and partly an object. Once it is an object, it can have properties. The way it is. And since this is a constructor function, it has a prototype property. The prototype property is a reference to an object that stores properties and methods that will go to the instances created by this constructor function. Let's try to display all this graphically.

By default, the prototype object is “empty” (well, almost empty, but more on that below). Above, I said that everything that lies in this object will go to the instance, and will also be available to descendants. That is, by default (if nothing is added to prototype), then the instance “nothing” will not be transferred from the constructor function “A”. That is, when executing the code:
function A(){} var a = new A();
we get the "normal" (as far as possible in JS) object "a".
JS has already built many constructor functions. This is for example Number (), String (), etc. Let's digress for a while from the example and talk about the built-in constructor functions and Objects in general.
Objects (__ proto__).
From the previous article, we know that when creating (explicitly or not explicitly) objects by one of the built-in constructors Number (), String () or Boolean (), an instance gets access to some methods characteristic of this type. For example, for Number () there is a toPrecision () method. If you look at the object created by the constructor new Number (2) in the console, you will not find this method there (you will not find any methods there at all). Where does he come from? Just he and similar methods (to which the descendant must have access) are contained in the prototype object of the parent. But how does the copy gain access to them? The instance has the property __proto__ - this is a link to the prototype object of the parent. If, when the method is called, the method is not in the instance itself, the link follows __proto__ to the prototype object of the parent and the search continues there. In fact, this goes on and on until null is met.
Let's try drawing it all:

Summing up, we can say that so far everything is not difficult: There is a parent (constructor function), which has a reference in the prototype property to a certain object where all methods and properties to which the descendant should have access are stored. And there is, in fact, a descendant to which, when created through a call to new from the parent, a link is passed to the __proto__ property to that very object with common properties and methods.
To fix, try to consider an example:
function A() {}
constructor.
I always took the word (empty) in quotes when I said (“empty” prototype). Like, when we create a function constructor function A () {}, the prototype property is created with reference to an “empty” prototype object. Not really. The prototype still has something. Firstly, since, as I already said, prototype is a “simple” Object, then there is the __proto__ property with a reference to the prototype of the Object () constructor function (it creates all the “simple”, the most elementary objects), and secondly there the constructor property lies. The constructor property is added there by the interpreter when it realizes that a constructor function is being created and not just an object. First, let's add our first drawing taking into account these two facts.

All that is painted in gray, we now do not particularly need - this is for a more complete picture. Focus on the constructor property. As seen from the figure, the constructor points to the constructor function for which this “storage” was originally created, this object. That is, there is a cyclicity between the prototype property of the constructor function and the constructor property of the prototype object — they point to each other's objects.
Through the constructor property (if it still points to the constructor, and the prototype property of the constructor, in turn, still points to the original prototype), you can indirectly get a reference to the object's prototype: a.constructor.prototype.x. And you can put a link to the constructor function itself and its properties that were assigned not to the prototype object, but specifically to it. For example:
function A(){} A.own = 'I am A!';
= {} - as a constructor function (new Object ()).
Great, sort of like everything fell into place. There is a “shared storage”, the parent and the descendant have links to this storage, if the property is not in the instance itself, then the interpreter will click on the link to look for it in the “shared storage”. What is the catch ?? Let's see Example2:
function B() {} B.prototype.x = 10; b = new B(); console.log(bx);
It seems like everything should work. We created a constructor function, set a “shared storage” (prototype (via link)) property (x), created an instance, it has property (x) - everything is fine. Then we generally redefined the prototype parent property by adding properties (x) and (y) and specifying the correct constructor. Everything should work in the “shared storage is” both of these properties, but no, (y) the interpreter does not find it. WTF?!?
What is the magic going on here? Why we do not see these changes from the descendant of this constructor? Why doesn't the descendant see y? Well, firstly, we override the prototype property of the constructor function (B) and it starts to refer to the new object (the connection with the original prototype object is broken). Secondly, the usual assignment of a variable to an object, of the type: var a = {}, by the interpreter is actually performed as var a = new Object (). And this means that the prototype property of the constructor function now contains a completely new object whose constructor reference is missing and in order not to lose the parent, we independently add the constructor property there and assign it to the parent itself.
And the instance made earlier contains the link __proto__ to the old prototype object where there are no properties (y). That is, unlike Example 1, here we did not “add a property to the repository” and did not even “rewrite the repository”, we just created a new one, breaking the link with the old one, and the instance knows nothing about it, it still uses the old one link __proto__. It looks like this:

In black, this is something that has not changed even after B.prototype = {constructor: B, x: 10, y: 20};
In red, what was removed
Green - what was added
You can also add a little bit about instanceof. Oddly enough, but in this example, b1 will belong to the constructor function B, but b will not. Everything is very simple. The fact is that instanceof is looking for the fulfillment of the following condition - so that the object specified by the link __proto __ (at any level of the chain) (circle with number 1) is equal to the object referred to by the prototype property of the desired parent (circle with number 2) (compare black in the figure color and green). In black, this condition is no longer fulfilled, and in green it is fulfilled.
In our case, for instance (b) this link is broken, since the new prototype property of the desired parent (B) refers to a new object, and not as before. But in the instance (b1) with this as we see everything is in order.
Chasing after
As for this in the body of the constructor function, I will not go deep at all - this will be discussed in the next article. The only thing I will say is that this when calling a function as a constructor (via new) will indicate the created instance, and when called as a function, it will point to a global object.
Let's take an example:
function A(str){ this.val = str; } a = new A('test');
How to learn as caused function? Through new or not? This is done very simply:
function A(str){ if( this instanceof A)
Approximately in such a way the type conversion mechanism is implemented. When executing, for example, 1 + '1', the interpreter perceives + as a string concatenation and tries to bring the number 1 into a string. This is done using the implicit call to String (1) (without new). And in the String constructor, we have about the same construction as above. That is, if the call occurred without new, simply return the string (an implicit call to the toString () method). Thus, without creating any objects, type conversion takes place.
I also want to add the following, in order to add a property to a function (namely, to a function and not to a prototype) I need to refer to it as an object. for example
function A(){} A.val = 'str';
This property will not be available to the descendant, since it does not lie in the prototype, and the descendant has access only there. But as they say "if you really want something you can." This is where the prototype object constructor comes in handy. As we remember, it refers to the function itself (unless of course this was specifically changed). Then to get the val variable, you need to call it like this:
function A(){} A.val = 'str'; a = new A(); a.constructor.val;
Sources:
https://developer.mozilla.orghttp://dmitrysoshnikov.com/http://javascript.ru