📜 ⬆️ ⬇️

Trying to just explain complex, for beginners, things in javascript

I will try to just explain how closures work in Javascript, how this works, how to create constructors for my classes and how different approaches to their creation differ.
The article does not pretend to be innovative, but I have not seen enough explanations for how it works for beginners, and in my opinion these are the three bottlenecks in Javascript (not tied to any context, server or browser, for example).

Closures

Wikipedia tells us - the closures are functions defined in other functions.
All functions are closures in javascript, because they lie implicitly in the body of the “Main function”.
What are they like? What does “closure” mean?
There is a very simple meaning behind the terminology, which can also be easily explained.
Closure functions have the ability to access variables created not only in the context of the function itself, but also at all levels above.
I will illustrate with the code:

var a = 1; var b = 2; function closureFirstLevel() { return a + b; } function createrOfSecondLevelClosure() { var c = a + b; return function() { return c + closureFirstLevel() + a + b; } } var c = createrOfSecondLevelClosure(); function runCInAnotherContext() { return c(); } console.log(a,b); console.log('  a & b       :',closureFirstLevel()); console.log('  c (   ),        ,   a  b       :',c()); 

Now we will understand a little, if something has become incomprehensible.
closureFirstLevel refers to variables declared outside of this function (external variables) and returns their sum.
createrOfSecondLevelClosure refers to the variables a and b, stores their sum in a variable declared in this function and returns a function that counts the sum c, the result returned by the function closureFirstLevel and the variables a and b declared two levels lower.

If you run runCInAnotherContext, it will run the function 'c' (after all, createrOfSecondLevelClosure returns us a function that can be saved, and the variable 'c', declared in the global scope, records this function), which will work as intended: return the sum of the variables and the result of the function, declared outside the context of the runCInAnotherContext function, since it initialized these variables during initialization.
')
Closures in mass creation of events.
A reference to a variable used in the loop as a counter is always transmitted as a reference (although usually a number) while the loop is running. As a result, all created functions will have the last value of this variable.
See example

 var elem = document.getElementsByTagName('a'); for (var n = 0, l = elem.length; n < l; n++ ) { elem[n].onclick = function() { alert(n); return false; } } //         alert 

You can close the function:

 var elem = document.getElementsByTagName('a'); for (var n = 0, l = elem.length; n < l; n++ ) { elem[n].onclick = function(x) { return function() { alert(x); return false; } }(n); // ,            alert   click  . } 

And you can also use a completely different approach. For arrays, the forEach method (part of the EcmaScript5 standard) does not quite work like a for loop.
It takes one argument - a function that will process the elements and which takes arguments: elementOfArray, positionInArray, Array. And each time this function is called, naturally, in its context.
Somewhere it is enough to take only the first argument, somewhere more.
We can call this function for our NodeList object, by changing the execution context. (For a more complete explanation of how this works, see the part of the article about this and about prototypes).

 var elem = document.getElementsByTagName('a'); Array.prototype.forEach.call(elem,function(el,position) { el.onclick = function() { alert(position); return false; } }) 


Keyword this

This word refers to the current object that is calling the function.
All functions declared in the global context are methods (in the browser) of the window object, so all functions called without the context in this refer to the window.
It's pretty simple, until you start to deal with asynchronous programming.

 var a = { property1: 1, property2: 2, func: function() { console.log(this.property1 + this.property2, 'test'); return this.property1 + this.property2; } } console.log(a.func()); //this    'a',    . setTimeout(function() { console.log(a.func()); //this       'a',   ,   ,     'a' },100); setTimeout(a.func,101); //   , NaN (   undefined + undefined) //,      ,          ,    

Instead of setTimeout, you can substitute setInterval or bind an event handler (for example, elem.onclick or addEventListener), or any other way to perform deferred calculations, all of which somehow cause a loss of execution context. And to save this there are several ways.
You can simply wrap it in an anonymous function, you can create a variable var that = this and use that instead of this (create a variable outside the called function, of course), as well as use the most correct way - to bind by force. For this, the functions have a built-in bind method (became available in the EcmaScript 5 standard, therefore, older browsers need to implement its support), which returns a new function tied to the desired context and arguments. Examples:

 function logCurrentThisPlease() { console.log(this); } logCurrentThisPlease(); //window var a = {} a.logCurrentThisPlease = logCurrentThisPlease; a.logCurrentThisPlease(); //a setTimeout(a.logCurrentThisPlease, 100); //window,         setTimeout(function() { a.logCurrentThisPlease(); }, 200);//a setTimeout(function() { this.logCurrentThisPlease(); }.bind(a), 200);//a var that = a; function logCurrentThatPlease() { console.log(that); } logCurrentThatPlease(); //a setTimeout(logCurrentThatPlease, 200);//a var logCurrentBindedContextPlease = logCurrentThisPlease.bind(a); //  — ,    ,   —   logCurrentBindedContextPlease(); //a setTimeout(logCurrentBindedContextPlease, 200); //a 

Well, more complicated example.
Loss in recursive functions working at regular intervals.

 var a = { i: 0, infinityIncrementation: function() { console.log( this.i++ ); if (this.i < Infinity) setTimeout(this.infinityIncrementation,500); } } a.infinityIncrementation(); // 0,undefined —  ,      a.infinityIncrementation = a.infinityIncrementation.bind(a); //     a.infinityIncrementation(); //0,1,2,3,4,5,6,7,8,9,10...Infinity-1 //  var b = { i: 0, infinityIncrementation: function() { console.log( this.i++ ); if (this.i < Infinity) setTimeout(function() {this.infinityIncrementation}.bind(this),500); } } b.infinityIncrementation(); //0,1,2,3,4,5,6,7,8,9,10...Infinity-1 

Why the second working method is correct, and the first one is wrong, see the part of the article about prototypes.

Function methods for changing the execution context - bind, call, apply

Function.bind is a method that takes the first argument as the context in which it will be executed (what this will be), and the rest as an unlimited number of arguments with which the returned function will be called.
Function.apply is the method that calls the function, the first argument is the argument that will be this in the function, the second is the array of arguments with which the function will be called.
Function.call is the same as apply, only instead of the second argument, an unlimited number of arguments that will be passed to the function.

Object constructors

Many create designers like this:

 function SuperObjectConstructor() { this.a = 1; this.b = 2; this.summ = function() { return this.a + this.b; } } 

And this is not very correct. What is wrong here? In this example, only one thing is wrong that the function is declared in the body of the constructor. What is this bad?
First, it will not be possible to redefine such a function through changing the prototype of the function, that is, all objects that were initialized through this constructor will not be able to change the method to another. The opportunity to inherit normally disappears.
Secondly - extra memory consumption:

 var a = new SuperObjectConstructor(); var b = new SuperObjectConstructor(); console.log(a.summ == b.summ); //false 

As each time the function is re-created.
According to a good tone (and for a better understanding of the code) in the constructors, you only need to define variables (more precisely, the fields of the object) that are unique to it.
It is better to determine the rest through a prototype, in any case, if only for a specific object it is necessary to override a common property or method, this can be done directly, without affecting the prototype.
How it's done:

 function SuperObjectConstructorRightVersion(a,b) { this.a = a || this.constructor.prototype.a; //      this.b = b || this.constructor.prototype.b; } SuperObjectConstructorRightVersion.prototype = { //   constructor: SuperObjectConstructorRightVersion, //          a: 1, b: 2, summ: function() { return this.a + this.b; } } /*   SuperObjectConstructorRightVersion.prototype.a = 1; SuperObjectConstructorRightVersion.prototype.b = 2; SuperObjectConstructorRightVersion.prototype.summ = function() {....};        . */ var abc = new SuperObjectConstructorRightVersion(); console.log(abc.summ());//3 var bfg = new SuperObjectConstructorRightVersion(5,20); console.log(bfg.summ());//25 

Many people lack javascript capabilities that are almost always needed only for self-control, such as private methods and functions that only the object and its methods can directly access, and often implement them as variables and functions declared in the function body. constructor. Also, many say that it is a bad tone, but where it is said very little why it is a bad tone.
There is one reason, that if in this constructor you need to change something, you will need to go to the source and change it there, and not through the prototype.
It will also be extremely difficult to inherit from this constructor in order to expand the use of these “private” properties and methods.

Another subtle point. Do not bind methods using bind to the context of the object (in the constructor during initialization, it is possible in principle), if you want to be able to transfer this method to other objects or simply use it in another context.
This allows us to do built-in objects.
For example, you can use the forEach array method for other enumerable (enumerable) objects. For example, for all kinds of NodeList (live and non-live) (as shown above).

Conclusion

And now let's write not a big constructor, as an example, combining the contents of the article.

 function Monster(name, hp, dmg) { this.name = name || this.constructor.prototype.name(); this.hp = hp || this.constructor.prototype.hp; this.dmg = dmg || this.constructor.prototype.dmg; } Monster.prototype = { constructor: Monster, hp: 10, dmg: 3, name: function() { return 'RandomMonster'+(new Date).getTime(); }, offerFight: function(enemy) { if (!enemy.acceptFight) { alert('this thing cant fight with me :('); return; } enemy.acceptFight(this); this.acceptFight(enemy); }, acceptFight: function(enemy) { var timeout = 50 + this.diceRollForRandom(); this.attack(enemy,timeout); }, diceRollForRandom: function() { return (Math.random() >= 0.5 ? 50 : 20); }, takeDmg: function(dmg) { console.log(this.name,' was damaged (',dmg,'),current HP is ',this.hp-dmg); return this.hp -= dmg; }, attack: function(enemy,timeout) { if (enemy.takeDmg(this.dmg) <= 0) { enemy.die(); this.win(); return; } this.to = setTimeout(function() {this.attack(enemy)}.bind(this),timeout); }, win: function() { alert('My name is ' + this.name + ', and Im a winner'); }, die: function() { alert('I died, ' + this.name); clearTimeout(this.to); } } var ChuckNorris = new Monster('Chuck Norris', 100, 100); var MikhailBoyarsky = new Monster('Misha Boyarsky', 200, 50); MikhailBoyarsky.offerFight(ChuckNorris); 

In this ridiculous example, in principle, there is everything: the preservation of the context of the call, the closure, and the creation of the constructor.
I hope for criticism and corrections (as I was in a hurry I could forget to add something, or I’m just mistaken).
ps boyars wins sometimes .

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


All Articles