📜 ⬆️ ⬇️

JavaScript closures

If you are using JavaScript, but you haven’t figured out that way, what a wonderful thing this is - closures , and why it is needed - this article is for you.

As is known, in JavaScript, the scope of local variables (declared by the word var) is the body of the function within which they are defined.

If you declare a function inside another function, the first one accesses the variables and arguments of the latter:
function outerFn (myArg) {
var myVar;
function innerFn () {
// has access to myVar and myArg
}
}
At the same time, such variables continue to exist and remain accessible by an internal function even after the external function in which they are defined has been executed.
')
Consider an example - a function that returns the number of own calls:
function createCounter () {
var numberOfCalls = 0;
return function () {
return ++ numberOfCalls;
}
}
var fn = createCounter ();
fn (); //one
fn (); // 2
fn (); // 3
In this example, the function returned by createCounter uses the variable numberOfCalls, which stores the desired value between its calls (instead of immediately ending its existence with the return of createCounter).

It is for these properties that such “nested” functions in JavaScript are called closures (a term that comes from functional programming languages) - they “close” to themselves the variables and arguments of the function within which they are defined.

Application closures


Let's simplify the example above a little bit - remove the need to separately call the createCounter function, make it anomimous and call immediately after its declaration:
var fn = ( function () {
var numberOfCalls = 0;
return function () {
return ++ numberOfCalls;
}
}) ();
Such a construction allowed us to bind data to a function that persists between its calls — this is one of the uses of closures. In other words, with the help of them we can create functions that have their changing state.

Another good use of closures is the creation of functions, which in turn also create functions, something that some would call a so-called trick. metaprogramming. For example:
var createHelloFunction = function (name) {
return function () {
alert ( 'Hello,' + name);
}
}
var sayHelloHabrahabr = createHelloFunction ( 'Habrahabr' );
sayHelloHabrahabr (); // alerts “Hello, Habrahabr”
Due to the closure, the returned function “remembers” the parameters passed to the function creating what we need for this kind of things.

A similar situation arises when we do not return the internal function, but hang it on an event - since the event occurs after the function has completed, the closure again helps not to lose the data transmitted during the creation of the handler.

Consider a slightly more complicated example - a method that binds a function to a specific context (that is, an object that the word this will point to in it).
Function.prototype.bind = function (context) {
var fn = this ;
return function () {
return fn.apply (context, arguments);
};
}
var HelloPage = {
name: 'Habrahabr' ,
init: function () {
alert ( 'Hello,' + this .name);
}
}
//window.onload = HelloPage.init; // alert would be undefined, tk this would point to a window
window.onload = HelloPage.init.bind (HelloPage); // now everything works
In this example, with the help of closures, a function bossed by the bind remembers the initial function and the context assigned to it.

The next, fundamentally different application of closures is data protection (encapsulation). Consider the following construction:
( function () {
...
}) ();
Obviously, inside the closure, we have access to all external data, but it also has its own. Due to this, we can surround parts of the code with a similar construction in order to close local variables that have fallen inside from access outside. (One of the examples of its use you can see in the source code of the jQuery library, which surrounds all its code with a closure, so as not to output the variables necessary only for it).

There is, however, one trap associated with such an application - inside the closure the meaning of the word this is lost outside of it. It is solved as follows:
( function () {
// the higher this will be saved
}). call ( this );

Consider another trick from the same series. Everywhere it was popularized by the developers of the Yahoo UI framework, calling it “Module Pattern” and writing an entire article about it in the official blog .

Let us have an object ( singleton ) containing any methods and properties:
var MyModule = {
name: 'Habrahabr' ,
sayPreved: function (name) {
alert ( 'PREVED' + name.toUpperCase ())
},
sayPrevedToHabrahabr: function () {
this .sayPreved ( this .name);
}
}
MyModule.sayPrevedToHabrahabr ();
With the help of a closure, we can make methods and properties that are not used outside the object private (i.e., accessible only to it):
var MyModule = ( function () {
var name = 'Habrahabr' ;
function sayPreved () {
alert ( 'PREVED' + name.toUpperCase ());
}
return {
sayPrevedToHabrahabr: function () {
sayPreved (name);
}
}
}) ();
MyModule.sayPrevedToHabrahabr (); // alerts "PREVED Habrahabr"

Finally, I want to describe a common mistake that drives many into a stupor if they do not know how the closures work.

Let us have an array of links, and our task is to make sure that when clicked on each one, its serial number is displayed by an alert. The first decision that comes to mind looks like this:
for ( var i = 0; i <links.length; i ++) {
links [i] .onclick = function () {
alert (i);
}
}
In fact, it turns out that when you click on any link, the same number is displayed - the value of links.length. Why it happens? In connection with the closure, the declared auxiliary variable i continues to exist, and at that moment when we click on the link. Since by that time the cycle has already passed, i remains equal to the number of links - this is the value we see with clicks.

This problem is solved as follows:
for ( var i = 0; i <links.length; i ++) {
( function (i) {
links [i] .onclick = function () {
alert (i);
}
}) (i);
}
Here, using another closure, we “shade” the variable i, creating its copy in its local scope at each step of the cycle. Thanks to this, everything now works as intended.

That's all. This article, of course, does not pretend to be exhaustive, but I hope someone will still help you figure it out. Thanks for attention!

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


All Articles