📜 ⬆️ ⬇️

Basics and Misconceptions About JavaScript

Objects, Classes, Constructors

ECMAScript, being a highly abstract object-oriented programming language, operates on objects. There are also primitives, but they, when required, are also converted into objects. An object is a collection of properties that also has a prototype object associated with it. The prototype is either an object, or null.
In JavaScript, there are no familiar classes, but there are constructor functions that spawn objects according to certain algorithms (see Operator new).

Prototype delegating inheritance


Classical inheritance is very similar to how people inherit the genes of their ancestors. There are some basic features: people can walk, talk ... And there are characteristics for each person. People are not able to change themselves - their class (but they can change their own properties) and grandmothers, grandfathers, moms and dads cannot dynamically influence the genes of children and grandchildren. Everything is very terrestrial.

Now let's imagine another planet on which there is no such gene inheritance as on Earth. There are mutants with "telepathic inheritance" who are able to change the genes of their descendants.
Let's look at an example. The Father inherits the genes from Grandfather, and the Son inherits the genes from the Father, who inherits from Grandfather. Each mutant can freely mutate, and can change the genes of its descendants. For example, Grandpa had green skin color, Father inherited color, Son inherited color too. Suddenly, the Grandfather decided: “I got tired of walking green — I want to be taken off,” he embarrassed (changed the prototype of his class) and “telepathically” spread this mutation to the Father and the Son, in general everything turned blue. Then Father thought: “Grandfather in his old age moved at all” and changed his color in the genes back to green (changed the prototype of his class), and distributed his color “telepathically” to his son. Father and Son are green, grandfather is blue. Now, no matter how much the grandfather tried, the Father and the son did not change the color, because now the Father prescribed a color in his prototype, and the Son will first of all inherit from the Father’s Prototype. Now the Son decides: “Let me change my color to black, and let my offspring inherit color from the Father” and prescribe my own property that does not affect the offspring. And so on.

We describe everything in the code:
var Grandfather = function () {}; //  Grandfather Grandfather.prototype.color = 'green'; var Father = function () {}; //  Father Father.prototype = new Grandfather(); //  ,        var Son = function () {}; //  Son Son.prototype = new Father(); //  var u = new Grandfather(); //  "" Grandfather var f = new Father(); //  "" Father var s = new Son(); //  "" Son //    console.log([u.color, f.color, s.color]); // ["green", "green", "green"] //         Grandfather.prototype.color = 'blue'; console.log([u.color, f.color, s.color]); // ["blue", "blue", "blue"] //          Father.prototype.color = 'green'; //     : // Grandfather.prototype.color = 'green'; console.log([u.color, f.color, s.color]); // ["blue", "green", "green"] //   Grandfather.prototype.color = 'blue'; console.log([u.color, f.color, s.color]); // ["blue", "green", "green"] //             s.color = 'black'; //   ,      console.log([u.color, f.color, s.color]); // ["blue", "green", "black"] var SonsSon = function () {}; //  SonsSon SonsSon.prototype = new Son(); //  var ss = new SonsSon(); //  "" SonsSon //      console.log([u.color, f.color, s.color, ss.color]); // ["blue", "green", "black", "green"] 

Read:
OOP in Javascript: Inheritance
Understanding prototype, __proto__, constructor and their chains in pictures
')

Chain of prototypes, getting the property with the given name


A prototype chain is a finite chain of objects that is used to organize inheritance and shared properties.

In JavaScript, each object has its own properties (Own Properties) and a reference to the prototype object, in turn, the prototype also has its own properties and a reference to the prototype, the prototype prototype also has its own properties and a reference to the prototype and so on, while the prototype link will not be null - this structure is called a chain of prototypes.
When you try to access the property of the object (through a dot or parentheses), the pointer is searched by name: first, it is checked if there is a pointer with such a name with its list of its own properties (if it has it, it returns), if it does not, then it is searched in its own prototype (if there is, then it is returned), if it is not there, then a prototype prototype is searched, and so on, until the prototype prototype becomes null in this case it returns undefined.

Some JavaScript implementations use the __proto__ property to represent the next object in the prototype chain.

The search for a read property can be described by the following function:
 function getProperty(obj, prop) { if (obj.hasOwnProperty(prop)) return obj[prop] else if (obj.__proto__ !== null) return getProperty(obj.__proto__, prop) else return undefined } 

For example, consider a simple Point 2D class that contains 2 properties (x, y) and a print method. Using the definitions above, we construct the object.
 var Point = { x: 0, y: 0, print: function () { console.log(this.x, this.y); } }; var p = {x: 10, __proto__: Point}; //  'x'    : /* px */ getProperty(p, 'x'); // 10 //  'y'    __proto__  - Point /* py */ getProperty(p, 'y'); // 0 //  print    __proto__  - Point /* p.print() */ getProperty(p, 'print').call(p); // 10 0 
Why I used call , but did not call the received function directly, is described below.

In fact, Point has another property, yes, this is our reference to the prototype of the parent __proto__ , which in the case of Point points to Object.prototype .
For example, this is how the whole chain of prototypes will look like in the very first example:
  /* SonsSon <- Son <---- Father <- Grandfather <-- Object <-- null */ console.log(ss.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__ === null); 

__proto__, prototype, operator new


Above was the "low-level code", now let's see how everything works in life:
 function Point(x, y) { //  Point this.x = x; this.y = y; } Point.prototype = { //   print: function () { console.log(this.x, this.y); } }; var p = new Point(10, 20); //    p.print(); // 10 20 

If in the previous code we at least knew what it was referring to, everything is somehow confusing.

All the "magic" is in the new operator. Brendan Eich (creator of JavaScript) wanted JavaScript to be similar to traditional OO languages, such as C ++, Java, so the new operator was added. Let's see how it works.

The new operator gets a function and function arguments ( new F(arguments...) ) and performs the following actions:

1. Creates an empty object with a single __proto__ property that refers to F.prototype
2. Executes the F constructor in which this is the previously created object
3. Returns an object
Create a New function that emulates the behavior of the new operator:
 function New (F, args) { /*1*/ var n = {'__proto__': F.prototype}; /*2*/ F.apply(n, args); /*3*/ return n; } 

Let's change the previous example with Point:
 function Point(x, y) { //  Point this.x = x; this.y = y; } Point.prototype = { //   print: function () { console.log(this.x, this.y); } }; var p1 = new Point(10, 20); p1.print(); // 10 20 console.log(p1 instanceof Point); // true //  : var p2 = New(Point, [10, 20]); p2.print(); // 10 20 console.log(p2 instanceof Point); // true 

Prototype Chaining


In the very first example, I built a prototype chain using the following construction Father.prototype = new Grandfather() :
 var Grandfather = function () {}; //  Grandfather Grandfather.prototype.color = 'green'; var Father = function () {}; //  Father Father.prototype = new Grandfather(); //  ,        var Son = function () {}; //  Son Son.prototype = new Father(); //  

Now we know the behavior of the new operator and we can understand what is being done here - we will expand the new Grandfather() :
 Father.prototype = { __proto__: { //  Grandfather color: 'green', __proto__: Object.prototype } }; 

Now, when we call new Father() we get the following object (we will immediately expand the object):
 Son.prototype = { __proto__: { //  Father __proto__: { //  Grandfather color: 'green', __proto__: Object.prototype } } } 

Let's see what we have at the end of the code in the s object (Son instance)
 { color: 'black', //      __proto__: { //  Son __proto__: { //  Father color: 'green', //     __proto__: { //  Grandfather color: 'blue', //      __proto__: Object.prototype } } } } 

Why is Father.prototype = new Grandfather() not the best way to build a chain of prototypes?
Because we have to call the Grandfather constructor, which can mix extra properties and call extra methods, for example alert . To work around this problem, use the fake constructor:
 function inherit (object, parent) { function F(){}; //   F.prototype = parent.prototype; //     object.prototype = new F(); //       return object; //     }; 

Example of use:
 var Grandfather = function () {}; //  Grandfather Grandfather.prototype.color = 'green'; var Father = function () {}; //  Father inherit(Father, Grandfather); //   

Grandfather Designer will not be executed. If we all need to do the Grandfather constructor, then call it using call or appy.
 var Father = function () { //  Father Grandfather.call(this); }; 


Instanceof operator


 if (p instanceof Point) { // ... } 

The instanceof operator is very closely related to chained prototypes. It uses exactly the prototype chain for rendering a verdict, and does not check whether the given object “p” is generated by the “Point” constructor. In this moment there is often confusion.

The instanceof operator operates on two objects — obj and constructor: ( obj instanceof constructor ). Starting from constructor.prototype, it runs through a chain of prototypes and tests the following equality obj.__proto__ === constructor.prototype , if it is true, then it returns true.

We describe in the code:
 function isInstanceOf(obj, constructor) { if (obj.__proto__ === constructor.prototype) return true; else if (obj.__proto__ !== null) return isInstanceOf(obj.__proto__, constructor) else return false } 

Consider the example above:
 function Point(x, y) { //  Point this.x = x; this.y = y; } Point.prototype = { //   print: function () { console.log(this.x, this.y); } }; var p = new Point(10, 20); //    /* {} instanceof Object */ console.log(isInstanceOf({}, Object)); // true /* p instanceof Point */ console.log(isInstanceOf(p, Point)); // true /* p instanceof Object */ console.log(isInstanceOf(p, Object)); // true ,  Object     (Point.__proto__ === Object.prototype) /* p instanceof Array */ console.log(isInstanceOf(p, Array)); // false ,  Array     

This property


this is one big misconception.

Many are used to the fact that the this keyword in programming languages ​​is closely related to object-oriented programming, that is, it points to the current object generated by the constructor. In ECMAScript, this is not limited to the definition of the object being spawned.
In JavaScript, the value of this is determined by the caller on the form of the call. The rule that determines what will be in this is (I will explain in simple terms):
1. If the method is called directly (without new, call, apply, bind, with, try catch ), then the value of this will be the object that stands before the point to the left of the method name.
2. If there is no point (the function is called directly), then this will be equal to undefined, null or window (global), depending on the environment and “use strict”.
3. If the expression is not a link, but a value, then clause 2 applies.

Example:
 var foo = { bar: function () { console.log(this); } }; var bar = foo.bar; bar(); // this === global (2) foo.bar(); // this === foo (1) (foo.bar)(); // this === foo     (1) //       -  (foo.bar = foo.bar)(); // this === global (3) (false || foo.bar)(); // this === global (3) (foo.bar, foo.bar)(); // this === global (3) function foo() { function bar() { console.log(this); } bar(); // this === global (2) } 

Recall the example of getProperty(p, 'print').call(p) because of this rule I manually specified the value of this. Otherwise, the print function would receive as this - window.

These operators and methods are capable of controlling the value of this: new, call, apply, bind, with, try catch (everything is more or less clear with them, I will not touch it).

More about this:
Subtleties ECMA-262-3. Part 3: This

undefined, null, void


null - a primitive value representing a null, empty, non-existent link
undefined - a primitive value that each variable gets by default (when a variable has no value)
void is an operator (i.e., when calling its parenthesis is not needed), it executes the expression and always returns undefined

Conclusion


1. There are no classes in JavaScript - there are constructors
2. Chain of prototypes - the base on which all inheritance in JavaScript is based
3. The property of the object is obtained using a chain of prototypes.
4. __proto__ is the reference to the prototype of the constructor (prototype)
5. The new operator creates an empty object with the only property __proto__, which refers to F.prototype, executes the F constructor in which this is the previously created object and returns the object
6. The instanceof operator does not check whether the given “Object” object is generated by the “ObjectsConstoructor” constructor; it uses a chain of prototypes for its verdict
7. In JavaScript, the value of this is determined by the caller on the form of the call.
8. void is an operator, not a function. undefined, null - primitive values

Important In some implementations of JavaScript, you cannot directly change __proto__ , besides, this property is not standard and already obsolete. To get a reference to the prototype, use Object.getPrototypeOf . In the article, I used it (__proto__) to demonstrate the "insides" of ECMAScript.

The article was used materials


Articles from Dmitry Soshinkov blog dsCode
How Prototypal Inheritance really works (Christopher Chedeau)

Source: https://habr.com/ru/post/120193/


All Articles