Introduced
In this chapter we will cover the topics that are often discussed in JavaScript-closures (closure). Closure in fact, everyone has been talking rotten. Still, try to discuss the closure from a theoretical perspective, and see how the closure inside the ECMAScript works.
As mentioned in the previous article, these articles are a series of articles that are related to each other. Therefore, in order to better understand the content to be introduced in this article, it is recommended to read the 14th chapter of the scope chain and chapter 12th variable objects.
Original English: http://dmitrysoshnikov.com/ecmascript/chapter-6-closures/
Introduction
Before discussing ECMAScript closures directly, it is important to look at some basic definitions in functional programming.
As we all know, in functional languages (ECMAScript also support this style), functions are data. For example, a function can be assigned to a variable, it can be passed to another function, and it can be returned from a function, and so on. This type of function has a special name and structure.
Defined
A functional argument ("Funarg")-is an argument the value is a function.
The function argument ("Funarg")--refers to the parameter of the value as a function.
Example:
Copy Code code as follows:
function Examplefunc (funarg) {
Funarg ();
}
Examplefunc (function () {
Alert (' Funarg ');
});
The actual parameter of Funarg in the above example is actually an anonymous function passed to Examplefunc.
In turn, functions that accept functional arguments are called higher-order functions (High-order function Abbreviation: HOF). It can also be called a functional function, or a partial mathematical or operator character. In the example above, Examplefunc is such a function.
As mentioned earlier, a function can be used not only as a parameter, but also as a return value. This class of functions that return a value is called a function with a function value (functions with functional value or function valued functions).
Copy Code code as follows:
(function functionvalued () {
return function () {
Alert (' returned function is called ');
};
})()();
Functions that can exist as normal data (for example, when a parameter is passed, a function parameter is accepted, or returned as a function value) are called first class functions (usually the first class object). In ECMAScript, all functions are the first class object.
Functions can exist as normal data (for example, when a parameter is passed, a function argument is accepted, or a function value is returned), which is called the first class function (generally, the first class object).
In ECMAScript, all functions are the first class object.
A function that accepts itself as a parameter, called a auto-applicative function or self-applicative functions:
Copy Code code as follows:
(function selfapplicative (funarg) {
if (funarg && funarg = = selfapplicative) {
Alert (' self-applicative ');
Return
}
Selfapplicative (selfapplicative);
})();
A function that returns a value of its own is called a auto-replicative function or a self-replicative functions. In general, the word "self-duplication" is used in literary works:
Copy Code code as follows:
(function selfreplicative () {
return selfreplicative;
})();
One of the more interesting patterns of the self-copying function is that it accepts only one item of the collection as a parameter instead of accepting the collection itself.
Copy Code code as follows:
Functions that accept Collections
function Registermodes (modes) {
Modes.foreach (Registermode, modes);
}
Usage
Registermodes ([' roster ', ' accounts ', ' groups ']);
Declaration of a self-copying function
function modes (mode) {
Registermode (mode); Register a mode
return modes; Returns the function itself
}
Usage, modes chained call
Modes (' roster ') (' accounts ') (' groups ')
Somewhat similar: Jqueryobject.addclass ("a"). Toggle (). Removclass ("B")
But the direct transmission set is relatively effective and intuitive.
Variables defined in a function argument are accessible when "Funarg" is activated (since the variable object that stores the context data is created each time it enters the context):
Copy Code code as follows:
function Testfn (funarg) {
When Funarg is activated, the local variable Localvar can access the
Funarg (10); 20
Funarg (20); 30
}
TESTFN (function (ARG) {
var localvar = 10;
Alert (arg + Localvar);
});
However, we know from chapter 14th that in ECMAScript, functions can be encapsulated in parent functions and can use variables of the parent function context. This feature can cause funarg problems.
Funarg problem
In a stack-oriented programming language, the local variables of a function are stored on the stack, and the variables and function parameters are pressed onto the stack whenever the function is activated.
When the function returns, the parameters are removed from the stack. This model has great limitations on the use of functions as functional values (for example, as return values from the parent function). In most cases, the problem arises when the function has a free variable.
A free variable is a variable that is used in a function but is neither a function parameter nor a local variable of a function.
Example:
Copy Code code as follows:
function Testfn () {
var localvar = 10;
function Innerfn (innerparam) {
Alert (Innerparam + Localvar);
}
return INNERFN;
}
var somefn = Testfn ();
Somefn (20); 30
In the example above, for the INNERFN function, the Localvar is a free variable.
For a system that uses a stack-oriented model to store local variables, it means that when the TESTFN function call ends, its local variables are removed from the stack. As a result, an error occurs when a function call is made externally to the INNERFN (because the Localvar variable is no longer present).
Furthermore, the above example is not possible in a stack-oriented implementation model to return INNERFN to return values. Because it is also a local variable of the TESTFN function, it is also removed as the TESTFN returns.
Another problem is that when the system uses a dynamic scope, the function is related as a function parameter.
See the following example (pseudo code):
Copy Code code as follows:
var z = 10;
function foo () {
alert (z);
}
Foo (); 10– when using static and dynamic scopes
(function () {
var z = 20;
Foo (); 10– using static scopes, 20– using dynamic scopes
})();
The same is the case for Foo as a parameter
(function (Funarg) {
var z = 30;
Funarg (); 10– static scopes, 30– dynamic scopes
}) (foo);
We see that the system using dynamic scope, variable (identifier) is managed by variable dynamic stack. Therefore, the free variable is queried in the active dynamic chain, rather than in the static scope chain that was saved when the function was created.
This can lead to conflict. For example, even if z still exists (in contrast to the previous example of removing a variable from the stack), there is the question of which Z's value is to be taken in different function calls (from which context, which scope is queried)?
The above describes two types of funarg problems--depending on whether the function is returned as a return value (the first type of problem) and whether the function is used as a function parameter (the second type of problem).
In order to solve these problems, the concept of closure is introduced.
Closed Bag
A closure is a combination of code blocks and data in the context in which the code block is created.
Let's look at the following example (pseudo code):
Copy Code code as follows:
var x = 20;
function foo () {
alert (x); Free variable "x" = = 20
}
for Foo closures
Fooclosure = {
Call:foo//reference to function
Lexicalenvironment: {x:20}//Context of search context
};
In the example above, the "Fooclosure" section is pseudo code. Correspondingly, in ECMAScript, the "foo" function already has an intrinsic property--the scope chain that creates the function context.
"Lexical" is usually omitted. The above example is to emphasize that the context data is saved while the closure is being created. The next time the function is called, the free variable can be found in the context of the saved (closure), as shown in the preceding code, and the value of the variable "z" is always 10.
The more generalized term we use in the definition-"code block", however, usually (in ECMAScript) uses the functions we use frequently. Of course, not all closure implementations bind closures and functions, for example, in a Ruby language, closures can be: a Process object (procedure object), a lambda expression, or a block of code.
The implementation of a stack is clearly not applicable (because it contradicts the stack based structure) for the implementation of preserving local variables after the context is destroyed. Thus, in this case, the upper-scope closure data is implemented by dynamically allocating memory (based on the "heap" implementation), with the use of the garbage collector (garbage collector) and the reference count (reference counting). This implementation is less than based on the performance of the stack, however, any implementation can always be optimized: You can analyze whether a function uses a free variable, a functional parameter, or a function value, and then decide based on the situation--whether the data is stored on the stack or in the heap.
Implementation of ECMAScript closure
After discussing the theoretical part, let's explain how the closure of the ECMAScript is implemented. It is also necessary to emphasize again that ECMAScript uses only static (lexical) scopes (and languages such as Perl, which can use static scopes as well as variable declarations using dynamic scopes).
Copy Code code as follows:
var x = 10;
function foo () {
alert (x);
}
(function (Funarg) {
var x = 20;
The variable "x" is saved statically in the (lexical) context, and is saved when the function is created
Funarg (); 10, not 20.
}) (foo);
Technically, the data that creates the parent context of the function is stored in the function's internal property [[Scope]]. If you are not aware of what [[scope]] is, it is recommended that you read chapter 14th, which gives a very detailed description of [[scope]]. If you fully understand the [[Scope]] and scope chain knowledge, then the closure is fully understood.
Based on the algorithm created by the function, we see that in ECMAScript, all functions are closures, because they all hold the scope chain of the upper context (except for the exception) at the time of creation (regardless of whether the function is subsequently activated--[[scope]) at the time the function was created:
Copy Code code as follows:
var x = 10;
function foo () {
alert (x);
}
Foo is a closed pack
Foo: <FunctionObject> = {
[[Call]]: <code block of Foo>
[[Scope]]: [
Global: {
X:10
}
],
.../other properties
};
As we said, in order to optimize the purpose, when a function does not use the free variable, the implementation may not be guaranteed to exist in the side-effect domain chain. However, nothing was said in the ecma-262-3 specification. Therefore, normally, all parameters are stored in the [[Scope]] attribute during the creation phase.
In some implementations, direct access to the closure scope is allowed. For example rhino, for the [[Scope]] attribute of a function, a non-standard __parent__ attribute should be introduced in chapter 12th:
Copy Code code as follows:
var global = this;
var x = 10;
var foo = (function () {
var y = 20;
return function () {
alert (y);
};
})();
Foo (); 20
alert (FOO.__PARENT__.Y); 20
FOO.__PARENT__.Y = 30;
Foo (); 30
Can be moved to the top through the scope chain
Alert (foo.__parent__.__parent__ = = global); True
alert (foo.__parent__.__parent__.x); 10
All objects refer to one [[Scope]]
Also note here: in ECMAScript, the closure created in the same parent context is a common [[Scope]] property. In other words, a closure of a variable in [[Scope]] will affect the other closures ' reading of its variables:
This means that all internal functions share the same parent scope
Copy Code code as follows:
var firstclosure;
var secondclosure;
function foo () {
var x = 1;
Firstclosure = function () {return ++x;};
Secondclosure = function () {return--x;};
x = 2; Influence ao["X"] in the 2 [[[Scope]] of the closed Bao
Alert (Firstclosure ()); 3, through the first closure [[Scope]]
}
Foo ();
Alert (Firstclosure ()); 4
Alert (Secondclosure ()); 3
There is a very common misconception about this feature that developers often do not get the expected results when creating a function in a looping statement (counting internally), and expect that each function has its own value.
Copy Code code as follows:
var data = [];
for (var k = 0; k < 3; k++) {
Data[k] = function () {
Alert (k);
};
}
Data[0] (); 3, not 0.
DATA[1] (); 3, not 1.
DATA[2] (); 3, not 2.
The above example proves that the closure created in the same context is a shared one [[Scope]] property. Therefore, the variable "K" in the upper context can easily be changed.
Copy Code code as follows:
Activecontext.scope = [
...//Other variable objects
{data: [...], k:3}//Active object
];
Data[0]. [[scope]] = = = Scope;
DATA[1]. [[scope]] = = = Scope;
DATA[2]. [[scope]] = = = Scope;
In this way, when the function is activated, the last K used is already turned into 3. As shown below, creating a closure can solve this problem:
Copy Code code as follows:
var data = [];
for (var k = 0; k < 3; k++) {
DATA[K] = (function _helper (x) {
return function () {
alert (x);
};
}) (k); Pass in "K" value
}
Now the results are correct.
Data[0] (); 0
DATA[1] (); 1
DATA[2] (); 2
Let's take a look at what happened to the above code. After the function "_helper" is created, it is activated by passing in the parameter "K". The return value is also a function, which is stored in the corresponding array element. This technique produces the effect that every time "_helper" creates a new variable object, which contains the parameter "x", and the value of "X" is the value of the passed in "K" when the function is activated. As a result, the returned function's [[Scope]] becomes the following:
Copy Code code as follows:
Data[0]. [[Scope]] = = = [
...//Other variable objects
Active object in parent context ao: {data: [...], k:3},
Active object in _helper context ao: {x:0}
];
DATA[1]. [[Scope]] = = = [
...//Other variable objects
Active object in parent context ao: {data: [...], k:3},
Active object in _helper context ao: {x:1}
];
DATA[2]. [[Scope]] = = = [
...//Other variable objects
Active object in parent context ao: {data: [...], k:3},
Active object in _helper context ao: {x:2}
];
We see that the [[scope]] property of the function has the actual desired value, and for that purpose we have to create an additional variable object in [[scope]]. Note that if you want to get the value of "K" in the returned function, the value will still be 3.
Incidentally, it is wrong to say that a large number of JavaScript-speaking articles think that only the additional functions created are closures. In practice, this approach is most effective, however, in theory, all functions in ECMAScript are closures.
However, the method mentioned above is not the only method. You can also get the correct value of "K" in other ways, as follows:
Copy Code code as follows:
var data = [];
for (var k = 0; k < 3; k++) {
(Data[k] = function () {
alert (arguments.callee.x);
}). x = k; To use K as a property of a function
}
And the result is right.
Data[0] (); 0
DATA[1] (); 1
DATA[2] (); 2
Funarg and return
Another feature is the return from the closure. In ECMAScript, the return statement in the closure returns the control flow to the calling context (the caller). In other languages, for example, Ruby, there are many forms of closures, and the corresponding closure returns are also different, and the following are possible: either directly to the caller or, in some cases, directly from the context.
The exit behavior of the ECMAScript standard is as follows:
Copy Code code as follows:
function GetElement () {
[1, 2, 3].foreach (function (Element) {
if (element% 2 = 0) {
Return to the function "ForEach" function
Instead of returning to the GetElement function
Alert (' Found: ' + Element); Found:2
return element;
}
});
return null;
}
However, the following effects can be achieved with a try catch in ECMAScript:
Copy Code code as follows:
var $break = {};
function GetElement () {
try {
[1, 2, 3].foreach (function (Element) {
if (element% 2 = 0) {
"Back" from GetElement
Alert (' Found: ' + Element); Found:2
$break. data = element;
Throw $break;
}
});
catch (e) {
if (e = = $break) {
return $break. Data;
}
}
return null;
}
Alert (GetElement ()); 2
Theoretical version
It is explained here that developers often mistakenly interpret closure as a way to return internal functions from the parent context, or even to understand that only anonymous functions can be closures.
Again, because of the scope chain, all functions are closed (regardless of function type: Anonymous functions, FE,NFE,FD are closures).
Except for a class of functions, this is a function created through the functions constructor, because its [[Scope]] contains only global objects.
To better clarify the problem, we give 2 correct version definitions for closures in ECMAScript:
In ECMAScript, closures refer to:
From a theoretical point of view: all functions. Because they are all created, the upper-context data is saved. This is true even for simple global variables, because accessing a global variable in a function is equivalent to accessing the free variable, which uses the outermost scope.
From a practical standpoint: The following functions are considered closures:
Even if the context in which it was created has been destroyed, it still exists (for example, the intrinsic function is returned from the parent function)
A free variable is referenced in the code
Closed-pack usage combat
When used in practice, closures can create very elegant designs that allow customization of a variety of computing methods defined on the Funarg. The following is an example of array ordering, which takes a sort condition function as an argument:
Copy Code code as follows:
[1, 2, 3].sort (function (A, b) {
..//Sorting criteria
});
In the same example, the map method of an array maps the original array to a new array based on the conditions defined in the function:
Copy Code code as follows:
[1, 2, 3].map (function (Element) {
return element * 2;
}); [2, 4, 6]
With functional parameters, it is convenient to implement a search method and can support unrestricted search conditions:
Copy Code code as follows:
Somecollection.find (function (Element) {
return element.someproperty = = ' Searchcondition ';
});
There are also application functions, such as the common foreach method, that applies functions to each array element:
Copy Code code as follows:
[1, 2, 3].foreach (function (Element) {
if (element% 2!= 0) {
alert (element);
}
}); 1, 3
Incidentally, the apply and call methods of a function object can also be used as an application function in functional programming. Apply and call have been introduced in the discussion of "this", and here we see them as application functions-functions that are applied to parameters (in Apply is a parameter list, which is a separate parameter in call):
Copy Code code as follows:
(function () {
Alert ([].join.call (arguments, '; ')); 1;2;3
). Apply (this, [1, 2, 3]);
Closures also have another very important application--deferred calls:
Copy Code code as follows:
var a = 10;
settimeout (function () {
alert (a); After one second
}, 1000);
and a callback function.
Copy Code code as follows:
//...
var x = 10;
Only for example
Xmlhttprequestobject.onreadystatechange = function () {
When the data is ready, it is invoked;
Here, regardless of which context is created in the
Now the value of the variable "x" already exists.
alert (x); 10
};
//...
You can also create encapsulated scopes to hide secondary objects:
Copy Code code as follows:
var foo = {};
Class
(function (object) {
var x = 10;
Object.getx = function _getx () {
return x;
};
}) (foo);
Alert (Foo.getx ()); Get closure "x" –10
Summarize
This article introduces more theoretical knowledge about ecmascript-262-3, and I think these basic theories help to understand the concept of closure in ECMAScript. If you have any questions, I will reply to you in the comments.
Other Reference
- Javascript Closures (by Richard Cornford)
- Funarg problem
- Closures