📜 ⬆️ ⬇️

Closures in Javascript [Part 2]

The previous part .



Closures


Automatic garbage collection


ECMAScript uses automatic garbage collection. The specification does not define the details, leaving the developers to deal with this, and some implementations are known for giving very little importance to garbage collection operations. But the general idea is that if it becomes impossible to refer to an object (due to the lack of references to it, remaining available for the executable code), then it becomes accessible to the garbage collector and at some point any resources that used, will be released and returned to the system for reuse.

As a rule, this happens immediately after exiting the execution context. The scope chain structure, the activation / variable object and any objects created in the execution context, including function objects, are no longer accessible and thus become accessible to the garbage collector.
')

Creating closures


A closure is formed by returning a function object that was created in the context of executing a function call, from this function call, and assigning a reference to a nested function to a property of another object. Or, if you directly assign a reference to such an object of a function, for example, a global variable, a property of a globally accessible object or an object passed by reference as an argument to an external function call.
function exampleClosureForm(arg1, arg2){ var localVar = 8; function exampleReturned(innerArg){ return ((arg1 + arg2)/(innerArg + localVar)); } /*     ,   exampleReturned */ return exampleReturned; } var globalVar = exampleClosureForm(2, 4); 

Now the function object created in the execution context of the exampleClosureForm call cannot be processed by the garbage collected because it is referenced by a global variable and is still available, this object can even be executed by calling globalVar (n) .

But the incident is a bit more complicated, because now the global variable globalVar refers to the function object, this object was created with the [[scope]] property referring to the scope chain containing the activation / variables object belonging to the execution context in which it was created (and containing a global object). Now the activation object / variables cannot be processed by the garbage collector, and when the function object is executed, referenced by globalVar , the entire scope chain, which its [[scope]] property refers to, will be added to the scope of the execution context that is created each time calling the function object.

The closure is created. The object of a nested function has free variables, and the object of activation / variables that is in the chain of scopes of this function is the medium connecting them.

The activation / variable object is trapped because it is referenced in the scope chain assigned to the [[scope]] internal property of the function object referenced by the globalVar variable. The activation / variable object is saved along with its state, i.e. with the values ​​of its properties. In the process of resolving the scope of the execution context of calls to an internal function, identifiers corresponding to the named properties of this activation object / variables will be calculated, simply as properties of this object. The values ​​of these properties can still be read and set, even after exiting the execution context in which they were created.

In the example above, this activation / variable object has a state that represents the values ​​of the formal parameters, definitions of internal functions, and local variables at the moment when the external function terminates (its execution context has completed). The arg1 property has a value of 2 , arg2 is a value of 4 , localVar is a value of 8, and the property of exampleReturned is a reference to an object of a nested function that is returned from an external function. (In the following, for convenience, we denote this object of activation \ variables as ActOuter1.)

If you call exampleClosureForm again
 var secondGlobalVar = exampleClosureForm(12, 3); 

- then a new execution context will be created with a new activation object. And a new function object will be returned with its own separate [[scope]] property referring to the scope chain containing the activation object from the second execution context with the values ​​of the arg1 - 12 and arg2 - 3 arguments. (In the following, for convenience, we denote this activation object \ variables as ActOuter2.)

The second and distinct closure was created by the second call to exampleClosureForm .

Both of these function objects, created by executing exampleClosureForm , referenced by the global variables globalVar and secondGlobalVar, respectively, return the expression ((arg1 + arg2) / (innerArg + localVar)) . It performs several statements to four identifiers. How these identifiers are calculated determines the purpose and meaning of the closures.

Consider executing the function object referenced by globalVar : globalVar (2) . A new execution context is created and an activation object (let's call it ActInner1), which will be added to the top of the scope chain referenced by the [[scope]] property of the executed function object. ActInner1 gets the innerArg property after the formal parameter is assigned the value of the argument 2 . The chain of scopes of the new execution context will be the following: ActInner1-> ActOuter1-> global object.

The resolution of identifier names is accomplished through a chain of scopes , thus. to return the value of the expression ((arg1 + arg2) / (innerArg + localVar)) , the values ​​of these identifiers will be determined by searching for properties with the names corresponding to the identifiers in each object from the chain of visibility in turn.

The first object in the chain is ActInner1 and it has an innerArg property with a value of 2 . The remaining 3 identifiers correspond to ActOuter1 named properties; arg1 with a value of 2 , arg2 - 4, and localVar - 8 . The function call returns ((2 + 4) / (2 + 8)) .

Compare this with the execution of another identical function object referenced by secondGlobalVar : secondGlobalVar (5) . Let's call the activation object the new execution context ActInner2, the chain will be like this: ActInner2-> ActOuter2-> global object. ActInner2 returns innerArg as 5 and ActOuter2 returns arg1 , arg2 and localVar as 12 , 3 and 8, respectively. Return value: ((12 + 3) / (5 + 8)) .

If we run secondGlobalVar again, a new activation object will appear at the beginning of the scope chain , but ActOuter2 will still be the next object in the chain, and the values ​​of its named properties will again be used to resolve identifier names arg1 , arg2 and localVar .

It is in this way that nested ECMAScript functions obtain and maintain access to formal parameters, declarations of nested functions, and local variables of the execution context in which they were created. And thus, the creation of closures allows such a function object to continue to refer to these values, read and modify them, while it exists. The activation object / variables from the execution context in which the nested function was created remains in the chain of scopes referenced by the [[scope]] property of the function object until all references to the nested function are released and the function object will not be accessible to the garbage collector (along with all the objects that are unnecessary from now on in its chain of sight).

Nested functions can themselves have nested functions, i.e. nested functions returned by executing functions to form a closure can themselves return nested functions and create their own closures. With each nesting, the scope chain will receive an additional activation object, arising along with the execution context in which the nested function object was created. The ECMAScript specification requires that a chain of scopes be finite, but does not impose any restrictions on their length. Perhaps in some implementations in practice there are some limitations, but so far there have been no reports of specific quantities. Apparently, the number of possible further investments of functions is more than anyone might need.

What can be done with closures?


It may seem strange, but the answer to this question is absolutely everything. I'm talking about the fact that closures allow ECMAScript to emulate anything, limiting only the ability to conceive and implement this emulation. It’s a bit confusing and it’s probably better to start with something more practical.

Example 1: setTimeout with a function reference


Closures are often used to provide parameters for executing a function prior to its execution. For example, when the function should be passed as the first argument to the setTimeout function, common in web browser environments.

setTimeout assigns the execution time of a function (or a string of javascript code, but not in our case), accessible through the first argument, after an interval expressed in milliseconds (from the second argument). To use setTimeout, you need to call this function and pass a reference to the function object as the first argument and an interval in milliseconds as the second, but the reference to the function object cannot contain parameters for the scheduled execution of this function.

However, you can call another function that returns a reference to the object of the nested function, which we pass by reference to the setTimeout function. The parameters used by the nested function are sent when the function that returns it is called. setTimout executes the nested function without passing arguments, but this nested function still has access to the parameters provided by the call to the external function that returned it.
 function callLater(paramA, paramB, paramC){ /*      ,   : */ return (function(){ /*        setTimeout,     ,      ,     */ paramA[paramB] = paramC; }); } ... /*  ,       ,     .  ,        ,    .         : */ var functRef = callLater(elStyle, "display", "none"); /*   setTimeout,     ,   funcRef,   : */ hideMenu=setTimeout(functRef, 500); 

Example 2: Associating functions with object instance methods


There are many other circumstances where a reference to a function object is assigned so that the function will be executed some time in the future, in this case it will be useful to prepare parameters for the execution of this function that are not easily available at run time but unknown until the time of assignment.

One example would be a javascript object that was created to encapsulate interaction with a specific DOM element. It contains doOnClick , doMouseOver and doMouseOut methods for executing at the moment when the corresponding events fire on this DOM element, but there may be any number of instances of javascript objects created to associate with different DOM elements, and individual instances of objects may not know how they will be applied in the code in which they are created. These instances of objects cannot determine how they are globally referenced because they do not know which global variables (if any) will be assigned to them.

Therefore, the problem lies in the execution of the function that handles the event, which is associated with a particular instance of the javascript object and knows which method of this object to call.

The following example uses a simple generic closure-based function that associates an object instance with element event handlers. Suppose that executing an event handler results in calling a particular method of an object instance, to which an event object and a reference to the associated element are passed, and returning the value that this method returns.
 /*  ,       .       .      obj,   ,      ,   methodName -   . */ function associateObjWithEvent(obj, methodName){ /*   , , ,      DOM : */ return (function(e){ /*  ,     e  ,      DOM,    ,   IE,         ,   */ e = e||window.event; /*      obj,      methodName   ,          ,    ,     this ( , ..        ,      ) */ return obj[methodName](e, this); }); } /*  -  ,   DOM ,  ID      .      , ,      onclick, onmouseover  onmouseout,       . */ function DhtmlObject(elementId){ /*  ,     DOM  ( null,    )   ID  ,   .      el. */ var el = getElementWithId(elementId); /*  el      boolean     if ,   el   ,    ,    null - .  ,    ,    el    DOM. */ if(el){ /*       ,     associateObjWithEvent,   (    this)  ,     ,     .  associateObjWithEvent     ,      DOM .         javascript    . */ el.onclick = associateObjWithEvent(this, "doOnClick"); el.onmouseover = associateObjWithEvent(this, "doMouseOver"); el.onmouseout = associateObjWithEvent(this, "doMouseOut"); ... } } DhtmlObject.prototype.doOnClick = function(event, element){ ... //   doOnClick. } DhtmlObject.prototype.doMouseOver = function(event, element){ ... //   doMouseOver. } DhtmlObject.prototype.doMouseOut = function(event, element){ ... //   doMouseOut. } 

Thus, any instance of DhtmlObject can be associated with a DOM element without affecting the global namespace and without risking a collision with other instances of the DhtmlObject , and without having to consider how it is used by other code.

Example 3: Encapsulating Interconnected Functionality


Closures can be used to create additional scopes to group related and interdependent code in ways that minimize the risk of random interaction. Suppose we have a function that creates a string, and in order to avoid repeating the concatenation operation (and creating a set of intermediate strings), it is advisable to use arrays to store parts of the string in a certain order, and then output the result using the Array.prototype.join ( with an empty string as an argument). The array is used as a buffer for output, but if it is defined locally, inside a function, then each time the function is executed, it will be created anew, which is not necessary if only the variable contents of the array change with each function call.

One of the ways is to create a global variable for this array, so as not to recreate it every time you use it. But later, in addition to the global variable referring to the function that uses the buffer array, there will be another global property that refers to the array itself. As a result, it becomes more difficult to control the code, in case it is used somewhere else, its author will have to remember that both the function definition and the array definition need to be added. Also, such code is harder to combine with other code, because, in addition to checking the uniqueness of the function name, it is also necessary to check that the name of the array on which this function depends is also unique to the global namespace.

A closure allows a buffer array to be associated (and neatly packaged) with a function that depends on it, and this simultaneously allows you to save the property name that was set for the array outside the global namespace and without the risk of name conflicts and random interactions.

The trick is to create one additional execution context by executing, inline (in-line) an expression function that returns a nested function that will be used in external code. The buffer array will then be defined as a local variable of an expression function with inline (in-line) execution. Since the execution occurs once, the array is created only once, but is available to functions depending on it, with repeated uses.

In the following code, a function is created that returns an HTML string, most of which is unchanged, but this sequence of characters must contain variable information accessible through parameters when the function is called.

A reference to an object of a nested function is returned from the execution embedded in an expression function and assigned to a global variable, therefore this function object can be called as a global function. A buffer array is defined as a local variable in an external function expression. It is not laid out in the global namespace and is not recreated with each call to a function that uses it.
 /*   getImgInPositionedDivHtml       -,      -.      HTML,     DIV,   IMG,           . */ var getImgInPositionedDivHtml = (function(){ /* bufferAr - ,     - .            , ..         .     -  ,       . */ var buffAr = [ '<div id="', '', // 1, DIV ID  '" style="position:absolute;top:', '', // 3, DIV top  'px;left:', '', // 5, DIV left  'px;width:', '', // 7, DIV width 'px;height:', '', // 9, DIV height 'px;overflow:hidden;\"><img src= '', // 11, IMG URL '\" width= '', // 13, IMG width '\" height= '', // 15, IMG height '\" alt= '', // 17, IMG alt  '\"><\/div>' ]; /*    ,     -.           getImgInPositionedDivHtml(...) */ return (function(url, id, width, height, top, left, altText){ /*           */ buffAr[1] = id; buffAr[3] = top; buffAr[5] = left; buffAr[13] = (buffAr[7] = width); buffAr[15] = (buffAr[9] = height); buffAr[11] = url; buffAr[17] = altText; /*  ,            (   ,       ) */ return buffAr.join(''); }); //  -. })(); /*^^-  (inline)   - */ 

( ) , - , , . , .



Perhaps one of the most well-known applications of closures is the emulation of private instance variables in Douglas Crocford's ECMAScript objects . Which can be extended with any kind of scope scaffold structure containing embedded accessibility and visibility, including emulation of private static members of ECMAScript objects .

There are an infinite number of applications for closures, understanding how they work is probably the best guide to understanding how they can be used.

Random closures


If you make any nested function available outside the body of the function in which it was created, a closure is formed. Therefore, closures are very easy to create, with the result that javascript developers who do not understand that closures are a language feature use nested functions for various tasks without obvious consequences, not realizing that closures were created or what consequences could be.

Accidental creation of closures can have side effects, such as the memory leak problem in IE described in the next section; they can also affect code performance. The reason is not the closures themselves, in fact, if used carefully, they can significantly contribute to the creation of efficient code. The point is nested functions, the use of which may affect efficiency.

, DOM . , , onclick (link element).
 /*   ,        href            */ var quantaty = 5; /*         (     linkRef),       onclick,      quantaty   href        ,    true,  ,    ,    href,     . */ function addGlobalQueryOnClick(linkRef){ /*   linkRef     true ( ,     ) */ if(linkRef){ /*  -            onclick   */ linkRef.onclick = function(){ /*   -      href ,         */ this.href += ('?quantaty='+escape(quantaty)); return true; }; } } 

, addGlobalQueryOnClick , ( ). , addGlobalQueryOnClick , , ( -).

The above code does not take advantage of the fact that internal functions have become available outside the function in which they were created (that is, the resulting closures). Exactly the same effect could be achieved by defining a function to use as an event handler separately and then assigning the reference to this function to the property of the event handler. Only one function object would be created and all elements using this event handler would use a reference to this one function.
 /*   ,        href            */ var quantaty = 5; /*         (     linkRef),       onclick,      quantaty   href        ,    true,  ,    ,    href,     . */ function addGlobalQueryOnClick(linkRef){ /*   linkRef     true ( ,     ) */ if(linkRef){ /*          ,  ,     . */ linkRef.onclick = forAddQueryOnClick; } } /*   ,         ,         href  . */ function forAddQueryOnClick(){ this.href += ('?quantaty='+escape(quantaty)); return true; } 

. . , , , , .

-. ,
 function ExampleConst(param){ /*      -             */ this.method1 = function(){ ... //   }; this.method2 = function(){ ... //   }; this.method3 = function(){ ... //   }; /*     . */ this.publicProp = param; } 

Each time a constructor is used to create an object by executing new ExampleConst (n) , a new set of function objects is created, which act as its methods. That is, the more instances of an object are created, the more corresponding function objects are created.

Douglas Crockford’s javascript private field emulation technique uses a closure resulting from the assignment of references to the nested function objects to the open properties of the created object inside the constructor. But if the object's methods do not take advantage of the closures formed inside the constructor, then creating extra function objects for each instance will slow down the process of creating objects and require more resources.

prototype , , , .
 function ExampleConst(param){ /*      */ this.publicProp = param; } /*       -            prototype  */ ExampleConst.prototype.method1 = function(){ ... //   }; ExampleConst.prototype.method2 = function(){ ... //   }; ExampleConst.prototype.method3 = function(){ ... //   }; 


Internet Explorer


Internet Explorer browser (tested on versions 4-6 (6 is the current version at the time of this writing)) has a defect in the garbage collection system that prevents the processing of ECMAScript objects and some objects of the environment if these objects of the environment are part of a “circular” link. Environment objects in this case are any DOM Node objects (including the document object and its descendants) and ActiveX objects. If the circular reference contains one or more of them, then none of the participating objects will be released, and the memory they use will remain inaccessible to the system until the browser is closed.

— , . , 1 , 2, 2 , 3, 3 , 1. ECMAScript, 1, 2 3 , , , , . Internet Explorer, DOM Node ActiveX, , , . , .

Closures are very good at creating circular references. If the object of the function creating the closure is assigned, for example, as an event handler of the DOM Node object, and the link to this Node has been assigned to one of the properties of activation objects / variables in the scope of this function, then a circular reference will appear. DOM_Node.onevent -> function_object. [[Scope]] -> scope_chain -> Activation_object.nodeRef -> DOM_Node. This is very easy to do, and a brief viewing of the site that forms such a link in a piece of code common to all pages can use most of the system memory (sometimes all).

, , , , onunload IE, , . ( ) IE.

. 2004.
:
Martin Honnen.
Yann-Erwan Perio (Yep).
Lasse Reichstein Nielsen. ( )
Mike Scirocco.
Dr John Stockton.
Garrett Smith.

.

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


All Articles