📜 ⬆️ ⬇️

Closures in Javascript [Part 1]

Translation of an article by Richard Cornford Javascript Closures .


Introduction


Short circuit
A closure is an expression (usually a function) that can have free variables, along with an environment that binds these variables (that is, “closes” this expression).

Closures are among the most powerful features of ECMAScript (javascript), but they cannot be properly applied without understanding. Despite the fact that they are easy to create, even by accident, their creation can have detrimental effects, in particular, in some relatively common browser environments. To avoid accidental collisions with flaws and to take advantage of closures, it is necessary to understand their mechanism. This is highly dependent on the role of the scope chain in identifier resolution resolution and on the resolution of property names in objects.

The simplest explanation of the closure is that ECMAScript allows nested functions, function definitions and function expressions (function expressions) within the bodies of other functions. And these nested functions have access to all local variables, parameters and functions that are inside their external function (external functions). A closure occurs when one of these nested functions becomes available outside the function in which it was included, so it can be executed after the external function completes. At this point, she still has access to local variables, parameters, and internal function declarations (function declarations) of her external function. These local variables, parameters and function declarations (initially) have the same values ​​that were at the time of the completion of the external function and can interact with the internal function.
')
Unfortunately, a proper understanding of closures requires an understanding of the mechanisms behind them, and quite a few technical details. Although some of the algorithms defined in ECMA 262 are affected at the beginning of the following explanation, most cannot be omitted or simply reduced to a simplified form. If you are familiar with resolving object property names, you can skip this section, but only people who are already familiar with closures can afford to skip subsequent sections and stop reading right now and return to using them.

Resolving object property names


ECMAScript recognizes two categories of objects: native objects “Native Object” and objects of the environment “Host Object” and a subcategory of native objects called built-in objects “Built-in Objects” (ECMA 262 3rd Ed Section 4.3). Native objects belong to the language, and host objects are provided by the environment and can be, for example, a document object, DOM nodes, etc.

Native objects are free and dynamic containers of named properties (some implementations are not so dynamic when it comes to the subcategory of embedded objects, although this usually does not matter). Certain named properties of a native object store values ​​that can be referenced by another object (functions are also objects in this sense) or elementary values: String, Number, Boolean, Null, or Undefined. The primitive type Undefined is a bit unusual in the sense that you can set the property of an object to undefined , but the property will not be removed from the object; it will remain a named property of the object, which simply stores the value undefined .

The following is a simplified explanation of how the properties of objects are read and set, with as much internal details as possible, including internal details.

Assignment of values


Named properties of objects can be created, or the values ​​of existing named properties can be set, by assigning a value to a named property.
In this way,
var objectRef = new Object(); //   (generic)  javascript. 

A property named "testNumber" can be created like this:
 objectRef.testNumber = 5; /*  */ objectRef["testNumber"] = 5; 

The object did not have the testNumber property before assigning a value, but it was created after. Any subsequent assignments do not need to create this property, they will simply change its value.
 objectRef.testNumber = 8; /*  */ objectRef["testNumber"] = 8; 

Javascript objects have prototypes that can themselves be objects, as will be briefly described later, and these prototypes can have named properties. But this does not apply to assignment. If a value is assigned and the object does not have a property with the corresponding name, then this property will be created and the value assigned to it. If the object has such a property, its value will be reset.

Reading values


It is when reading values ​​that prototype objects are used. If the object has a property with the name used in the property accessor expression (property accessor), then the value of this property will be returned.
 /*    .           ,     */ objectRef.testNumber = 8; /*     */ var val = objectRef.testNumber; /*  val    8,        . */ 

But all objects can have prototypes, and prototypes are objects and, in turn, can have prototypes that can have prototypes, etc., forming what is called a chain of prototypes. A prototype chain ends when one of the objects in the chain has a null prototype. The default prototype with the Object constructor has a null prototype, so
 var objectRef = new Object(); //   (generic)  javascript 

creates an object with the prototype Object.prototype , which itself has a null prototype. Then the prototype chain of the objectRef object contains only one object: Object.prototype . But,
 /*  “”     MyObject1.*/ function MyObject1(formalParameter){ /*    testNumber    ,  ,     */ this.testNumber = formalParameter; } /*  “”     MyObject2 */ function MyObject2(formalParameter){ /*  testString      ,  ,     */ this.testString = formalParameter; } /*   ,   ,      MyObject2,   MyObject1,   8   MyObject1,     testNumber     */ MyObject2.prototype = new MyObject1( 8 ); /*,    MyObject2    objectRef    ,        */ var objectRef = new MyObject2( "String_Value" ); 

The instance of the MyObject2 object referenced by the objectRef variable has a prototype chain. The first object in this chain is an instance of the object MyObject1 , which was created and assigned to the prototype property of the constructor MyObject2 . An instance of MyObject1 has as its prototype an object that has been assigned to the prototype property of the MyObject1 function by default. This prototype is the default prototype of an Object , i.e. the object referenced by the Object.prototype . Object.prototype has a null prototype, so the chain ends at this point.

When a property access expression tries to read a named property from an object referenced by an objectRef , the whole prototype chain can participate in the process. In the simple case
 var val = objectRef.testString; 

- an instance of MyObject2 , accessible via objectRef , has a property named testString , therefore the value of this property is set to String_Value , and it is assigned to the variable val .
However,
 var val = objectRef.testNumber; 

cannot read a property from the very instance of the MyObject2 function, since it does not have this property, but the variable val is set to 8 , not undefined , because the interpreter checks the object, which is its prototype, due to an unsuccessful search for the corresponding named property in the object itself. Its prototype is an instance of the function MyObject1 , which was created with the testNumber property with a value of 8 assigned to this property, so the property access expression is calculated as the value 8 . Neither MyObject1 nor MyObject2 defines the toString method, but if the property access expression tries to read the value of the toString property from the objectRef ,
 var val = objectRef.toString; 

then the variable val is assigned a function reference. This function is the toString property of the Object.prototype object and is returned as a result of the prototype verification process: the objectRef object is checked , after the absence of the toString property is detected , the objectRef prototype is checked and when it turns out that it does not have this property, in turn its prototype is checked. Its prototype is the Object.prototype , which has a toString method and the link is returned to this function.

Finally:
 var val = objectRef.madeUpProperty; 

- returns undefined , because the process that processes the prototype chain does not find properties with the name madeUpProperty in any of the objects, it ultimately reaches the prototype of the Object.prototype object, i.e. null , and then the process ends, returning undefined .

Reading named properties returns the first value found from an object or from its prototype chain. Assigning a value to a named property of an object will create a property in the object itself if the corresponding property does not already exist.

This means that if the value was assigned to objectRef.testNumber = 3 , then the testNumber property will be created in the instance of the MyObject2 function and subsequent attempts to read the value will result in the return of the value that is set in the object. Now, the execution of the prototype chain processing is no longer required to execute the property access expression, but the instance of the MyObject1 object with the value 8 assigned to the testNumber property is not changed. Assigning a value to an objectRef simply hides the corresponding property in its prototype chain.

Note that ECMAScript defines an internal [[prototype]] internal property of type Object . This property is not directly accessible to scripts, but there is a chain of objects referenced by the [[prototype]] internal property, which is used in resolving access property expressions; this chain is a chain of object prototypes. The public property prototype exists for assigning values, defining, and manipulating prototypes along with the internal [[prototype]] property. Details of the relationship between these two properties are described in ECMA 262 (3rd edition) and are not included in this discussion.

Identifier Name Resolution, Execution Contexts, and Scoping Chain


Execution context


The execution context is an abstract concept that is used in the ECMAScript specification (ECMA 262 3rd edition) to define the behavior required from ECMAScript implementations. The specification says nothing about how the execution context should be implemented, but execution contexts have associative attributes that refer to structures defined in the specification, so they can be conceived (or even implemented) as objects with properties, albeit closed ones.

All javascript code is executed in execution context . The global code (embedded code in the html page or in the JS file or executed after the page loads (loads)) is executed in the global execution context and each function call (possibly as a constructor) has a corresponding execution context. The code executed using the eval function also receives a specific execution context, but since eval is usually not used by javascript programmers, it will not be discussed here. Specified details of execution contexts can be found in section 10.2 of ECMA 262 (3rd edition).

When the javascript function is called, it adds the execution context , if another function is called (or the same function recursively), a new execution context is created, and the execution process adds this context during the function call. When the called function is completed, the initial execution context is returned. Thus, the javascript executable code forms a stack of execution contexts .

When the execution context is created, several things happen in a certain order. First, in the execution context , an Activation object is created. The activation object is another mechanism from the specification. It can be considered as an object, because as a result it has available named properties, but it is not an ordinary object, since it does not have a prototype (at least, the prototype is not defined) and there can be no links to it in javascript code.

The next step in creating the execution context for a function call is to create a arguments object, this is a massive object with elements indexed by integers that correspond to the arguments sent to the function in the given order. It also has the length and callee properties (which are not relevant to our discussion, details in the specification). A property of the activation object is created with the name arguments , which is assigned a reference to the arguments object.

Then the execution context assigns a scope . The scope consists of a list (or chain) of objects. Each function object has an internal [[scope]] property (which we will soon discuss in more detail), which also consists of a list (or chain) of objects. The scope assigned to the execution context of a function call consists of the list referenced by the [[scope]] property of the corresponding function object, and of the activation object added to the beginning of the chain (or at the top of this list).

Then comes the process of creating variables (variable instantiation) using an object, which is defined in ECMA 262 as an object of variables (Variable object). At the same time, the activation object is used as an object of variables (note that this is one and the same object, this is important). Named properties of the variable object are created for each formal parameter of the function, and if the arguments in the function call correspond to these parameters, then the arguments are assigned to these properties (otherwise, they are assigned undefined ). The definition of internal functions is used to create function objects that are assigned to the properties of the variable object with the names corresponding to the function names used in the function declarations. The final stage in creating variables is to create named object properties for variables that correspond to all local variables defined inside the function.

Properties created in the object of variables that correspond to declared local variables initially get the value undefined during the creation of variables; local variables are not initialized until the corresponding assignment operation is performed during execution of the function body code.

In fact, an activation object with the arguments property and an object of variables with named properties corresponding to the local variables of the function are the same object that allows you to treat the arguments identifier as a local variable of the function.

Finally, the value used with the this keyword is assigned. If this value refers to an object, then property access expressions with the prefix of this refer to the properties of this object. If this value (assigned inside the function) is null , then this will refer to the global object.

The processing of the global execution context is slightly different, it has no arguments, so it does not need to define an activation object to refer to them. The global execution context needs a scope and its chain contains only one object - a global object. The global execution context goes through the creation of variables, its internal functions are the usual top-level declaration functions that make up most of the javascript code. A global object is used as an object of variables, which is why functions declared globally become properties of a global object. The same with globally declared variables.

The global execution context also uses the reference to the global object as the value of this .

The scope chain and the [[scope]] property


The scope chain of the execution context of a function call is assembled by adding an activation object (variable object) to the beginning of the scope chain contained in the [[scope]] property of the function object, so it is important to understand how the [[scope]] internal property is defined.

In ECMAScript, functions are objects, they are created during the creation of variables from function declarations, during the execution of a function-expression (Function Expression) or by calling the Function constructor.

Function objects created by the Function constructor always have the [[scope]] property referencing the scope chain that contains only the global object.

Function objects created by a function declaration or function-expression have the scope of the execution context in which they are created, which is assigned to their internal [[scope]] property.

In the simplest case, the declaration of a global function, such as
 function exampleFunction(formalParameter){ ... //    } 

the corresponding function object is created during the creation of variables for the global execution context. The global execution context has a scope consisting of only a global object.

, , exampleFunction , [[scope]] , , .

-
 var exampleFuncRef = function(){ ... //    } 

, , , , . , [[scope]] .

- , , . , ,
 function exampleOuterFunction(formalParameter){ function exampleInnerFuncitonDec(){ ... //    } ... //      } exampleOuterFunction( 5 ); 

, , , [[scope]] , .

exampleOuterFunction , ( ). , , , [[scope]] ( ). , , [[scope]] , . , .

. [[scope]] [[scope]] ( ). ECMAScript with .

with , ( /). with ( ) .

with , , - with
 /*    y,     */ var y = {x:5}; //     x function exampleFuncWith(){ var z; /*  ,     y,      */ with(y){ /*  -,    ,          z */ z = function(){ ... //   -; } } ... } /*   exampleFuncWith */ exampleFuncWith(); 

exampleFuncWith , , . with , y , , -. [[scope]] , -, , , . y , , .

with , ( y ), [[scope]] y .


. ECMA 262 this , , , .. this , , , .

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

, , . , , , . .

( ) , , , , , . ( ).

.

Javascript [ 2]

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


All Articles