Home Java Trying to simply explain complicated, for beginners, things in javascript

Trying to simply explain complicated, for beginners, things in javascript

by admin

I’ll try to simply explain how closures work in javascript, how this works, how to create constructors for your classes, and how different approaches to creating them differ.
This article doesn’t pretend to be groundbreaking, but I haven’t seen enough clear explanations of how it works for beginners, and in my opinion, these are the three Javascript bottlenecks (not tied to any context, server or browser, for example).

Closures

Wikipedia tells us – closures are functions defined in other functions.
Closures in javascript are all functions because they implicitly lie in the body of the "Main Function".
What do they represent? What does "closure" mean?
There is a very simple meaning behind the terminology that can just as easily be explained.
Closure functions have the ability to refer to variables created not only in the context of the function itself, but at all levels above.
I’ll illustrate with 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('Sum of variables a b declared outside the function that counts their sum :', closureFirstLevel());console.log('Sum of variables c (declared two levels higher), the return value of the function declared two levels higher, and variables a and b also declared two levels higher :', c());

Now let’s break it down a little bit, in case anything has become unclear.
closureFirstLevel accesses variables declared outside this function (external variables) and returns their sum.
createrOfSecondLevelClosure calls the a and b variables, stores their sum in a variable declared in this function, and returns a function that counts the sum of c, the result returned by closureFirstLevel, and variables a and b declared two levels lower.
If you runCInAnotherContext it will run the ‘c’ function (because createrOfSecondLevelClosure returns a function that we can store, and the ‘c’ variable declared in the global scope writes that function) which will work as intended: it will return the sum of variables and results of the function declared outside the runCInAnotherContext because it has locked those variables in its initialization.
Short circuits in mass event creation.
A reference to a variable used as a counter in a loop is always passed as a reference (though usually a number) as long as the loop is running. As a result, all functions created will have the last value of this variable.
See the example

var elem = document.getElementsByTagName('a');for (var n = 0, l = elem.length; n < l; n++ ) {elem[n].onclick = function() {alert(n);return false;}} //It will always issue the serial number of the last element in 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); //Create the function, immediately call it, it returns us the sequence number of the element in the alert at the click event on the element.}

And you can also use a completely different approach. With arrays, the forEach method (which is part of the EcmaScript5 standard) doesn’t work exactly like a for loop.
It takes one argument, a function that will handle elements and that takes arguments : elementOfArray, positionInArray, Array. And each time this function is called, naturally, in its own context.
Somewhere only the first argument is enough, somewhere more.
We can call this function for our NodeList object, by swapping the execution context. (See the part about this and prototypes for a fuller explanation of how this works.)

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 current object that calls the function.
All functions declared in global context are methods (in the browser) of the window object, in the same way all functions called without context in this refer to window.
It’s pretty simple, until you start dealing with asynchronous programming.

var a = {property1: 1, property2: 2, function: function() {console.log(this.property1 + this.property2, 'test');return this.property1 + this.property2;}}console.log(a.func());//this refers to the 'a' object that the called method belongs to.setTimeout(function() {console.log(a.func());//this still refers to the 'a' object because the function passed in the timeout has shorted the 'a' object}, 100);setTimeout(a.func, 101);//the result will be different, NaN (as a result of adding undefined + undefined)//because here we are only passing a function, which itself does not store a reference to the object it belongs to

Instead of setTimeout you can substitute setInterval or event handler bindings (e.g. : elem.onclick or addEventListener), or any other way to do delayed computation, all of which cause loss of execution context one way or another. And there are several ways to save this.
You can just wrap it in an anonymous function, you can create a variable var that = this and use that instead of this (create the variable outside the called function, of course), and you can use the most correct way – forcibly bind. To do this, functions have a built-in bind method (became available in the EcmaScript 5 standard, so for older browsers you need to implement support for it) that returns a new function bound to the right context and arguments. Examples :

function logCurrentThisPlease() {console.log(this);}logCurrentThisPlease(); //windowvar a = {}a.logCurrentThisPlease = logCurrentThisPlease;a.logCurrentThisPlease(); //asetTimeout(a.logCurrentThisPlease, 100); //window, since we only pass a function referencesetTimeout(function() {a.logCurrentThisPlease();}, 200);//asetTimeout(function() {This.logCurrentThisPlease();}.bind(a), 200);//avar that = a;function logCurrentThatPlease() {console.log(that);}logCurrentThatPlease(); //asetTimeout(logCurrentThatPlease, 200);//avar logCurrentBindedContextPlease = logCurrentThisPlease.bind(a); //the first argument is the context to be bind to, the other arguments are function argumentslogCurrentBindedContextPlease(); //asetTimeout(logCurrentBindedContextPlease, 200); //a

And a more complicated example.
Loss in recursive functions running at certain intervals.

var a = {i: 0, infinityIncrementation: function() {console.log( this.i++ );if (this.i < Infinity) setTimeout(this.infinityIncrementation, 500);}}a.infinityIncrementation(); // 0, undefined - doesn't work because the execution context is losta.infinityIncrementation = a.infinityIncrementation.bind(a); // not correct but worksa.infinityIncrementation(); //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10...Infinity-1//the correct wayvar 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 way works is right and the first way is wrong, see the prototypes part of the article.

Function methods to change execution context – bind, call, apply

Function.bind is a method that takes the first argument as the context in which it will be executed (which will be this), and the rest as an unlimited number of arguments with which the returned function will be called.
Function.apply is a method that calls a 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 to be passed to the function.

Object constructors

Many people create constructors so :

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

And that’s not very right. What’s wrong here? The only thing wrong in this example is that the function is declared in the constructor body. How is that wrong?
Firstly, it’s impossible to override such a function by changing the function prototype, that is, all the objects at once initialized through this constructor cannot change the method to another one. It makes it impossible to inherit normally.
Secondly – unnecessary memory consumption:

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

Since each time the function is re-created.
The good tone (and for better code understanding) is to define only variables (more precisely object fields) in constructors that will be unique to it only.
Everything else is better defined through the prototype, in any case if you only want to override a generic property or method for a specific object, you can do it directly without affecting the prototype.
How this is done :

function SuperObjectConstructorRightVersion(a, b) {this.a = a || this.constructor.prototype.a; //take the default value from the constructor prototypethis.b = b || this.constructor.prototype.b;}SuperObjectConstructorRightVersion.prototype = { //change the prototype completelyconstructor: SuperObjectConstructorRightVersion, // since we're replacing it completely, we need to redefine the constructor as wella: 1, b: 2, summ: function() {return this.a + this.b;}}/*or this waySuperObjectConstructorRightVersion.prototype.a = 1;SuperObjectConstructorRightVersion.prototype.b = 2;SuperObjectConstructorRightVersion.prototype.summ = function() {....};But it's less elegant and takes up more space.*/var abc = new SuperObjectConstructorRightVersion();console.log(abc.summ());//3var bfg = new SuperObjectConstructorRightVersion(5, 20);console.log(bfg.summ());//25

Many people miss features in javascript that are almost always just for self control, such as private methods and functions that only the object itself and its methods can directly access, and often implement them as variables and functions declared in the body of the constructor function. Also, many people say that this is a bad tone, but not much is said about why this is a bad tone.
One reason is that if you want to change something in this constructor, you have to go to the source and change it there, not through the prototype.
Also inheriting from this constructor to extend the use of these "private" properties and methods would be extremely difficult.
One more subtle point. Don’t bind methods to an object’s context (in the constructor during initialization, outside it in principle you can) if you want to be able to transfer the method to other objects or just use it in another context.
That’s what built-in objects allow us to do.
For example, we can use the forEach array method for other enumerable objects. For example, for all kinds of NodeLists (alive and not alive) (as shown above).

Output

Now let’s write a small constructor, as an example, which combines 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);

This ridiculous example basically has it all: saving the calling context, closures, and creating a constructor.
I hope for criticism and corrections (because I might have forgot to add something or just made a mistake).
p.s.boyarsky wins sometimes

You may also like