introduce
In this chapter, we will introduce the topic that everyone often discusses in JavaScript - closure. In fact, everyone has already talked about the closure. Nevertheless, we still want to try to discuss closures from a theoretical perspective and see how the closures in ECMAScript work.
As mentioned in the previous article, these articles are series of articles and are related to each other. Therefore, in order to better understand what this article is about to introduce, it is recommended to read Chapter 14 Scope Chain and Chapter 12 Variable Objects first.
Original English: /ecmascript/chapter-6-closures/
Introduction
Before directly discussing ECMAScript closures, it is still necessary to look at some basic definitions in functional programming.
As we all know, in functional languages (ECMAScript also supports this style), functions are data. For example, a function can be assigned to a variable, passed to other functions when parameters, and returned from the function, etc. This type of function has special names and structures.
definition
A functional argument (“Funarg”) — is an argument which value is a function.
Functional parameter ("Funarg") - refers to a parameter whose value is a function.
example:
function exampleFunc(funArg) {
funArg();
}
exampleFunc(function () {
alert('funArg');
});
In the above example, the actual parameters of funarg are actually an anonymous functions passed to exampleFunc.
Conversely, functions that accept functional parameters are called high-order functions (HOF). It can also be called: functional functions or partial mathematical or operators. In the above example, exampleFunc is such a function.
As mentioned earlier, functions can not only be used as parameters, but also as return values. This type of function with function value or function valued functions is called functions with function value.
(function functionValued() {
return function () {
alert('returned function is called');
};
})()();
Functions that can exist in normal data form (for example, when the parameters are passed, accept functional parameters or return as function values) are called first-class functions (generally speaking first-class objects). In ECMAScript, all functions are first-class objects.
Functions can exist as normal data (for example, when passing parameters, accepting functional parameters, or returning as function values) are called first-class functions (usually speaking first-class objects).
In ECMAScript, all functions are first-class objects.
A function that accepts itself as an argument is called an auto-applicative function or self-applicative function:
(function selfApplicative(funArg) {
if (funArg && funArg === selfApplicative) {
alert('self-applicative');
return;
}
selfApplicative(selfApplicative);
})();
Functions that take themselves as the return value are called auto-replicative functions or self-replicative functions. Usually, the word "self-copy" is used in literary works:
(function selfReplicative() {
return selfReplicative;
})();
One of the more interesting patterns of the self-copy function is to accept only one item of the set as a parameter instead of accepting the set itself.
// Functions that accept collections
function registerModes(modes) {
(registerMode, modes);
}
// Usage
registerModes(['roster', 'accounts', 'groups']);
// Declaration of self-copy function
function modes(mode) {
registerMode(mode); // Register a mode
return modes; // Return the function itself
}
// Usage, modes chain call
modes('roster')('accounts')('groups')
//Somewhat similar: ("a").toggle().removClass("b")
However, it is relatively effective and intuitive to use when passing the collection directly.
Variables defined in functional parameters can be accessed when "funarg" is activated (because the variable object that stores context data is created every time it enters the context):
function testFn(funArg) {
// When funarg is activated, the local variable localVar can be accessed
funArg(10); // 20
funArg(20); // 30
}
testFn(function (arg) {
var localVar = 10;
alert(arg + localVar);
});
However, we know from Chapter 14 that in ECMAScript, functions can be encapsulated in parent functions and can use variables in parent function context. This feature will cause funarg problems.
Funarg Problem
In stack-oriented programming languages, local variables of functions are stored on the stack. Whenever the function is activated, these variables and function parameters will be pushed onto the stack.
When the function returns, these parameters are removed from the stack. This model has great limitations on using functions as functional values (for example, returning from the parent function as a return value). In most cases, the problem occurs when the function has free variables.
Free variables refer to variables that are used in functions but are neither function parameters nor local variables of functions.
example:
function testFn() {
var localVar = 10;
function innerFn(innerParam) {
alert(innerParam + localVar);
}
return innerFn;
}
var someFn = testFn();
someFn(20); // 30
In the above example, for the innerFn function, localVar is a free variable.
For systems that use stack-oriented models to store local variables, it means that when the testFn function call is finished, its local variables will be removed from the stack. In this way, an error occurs when a function call is made to innerFn from the outside (because the localVar variable no longer exists).
Moreover, in the above example, in the stack-oriented implementation model, it is impossible to return innerFn with a return value. Because it is also a local variable of the testFn function, it will also be removed as the testFn returns.
Another problem is that when the system adopts dynamic scope and functions are used as function parameters.
See the following example (pseudocode):
var z = 10;
function foo() {
alert(z);
}
foo(); // 10 – When using static and dynamic scopes
(function () {
var z = 20;
foo(); // 10 – Using static scope, 20 – Using dynamic scope
})();
// It's the same when using foo as a parameter
(function (funArg) {
var z = 30;
funArg(); // 10 – Static scope, 30 – Dynamic scope
})(foo);
We see that systems that adopt dynamic scopes, variables (identifiers) are managed through variable dynamic stacks. Therefore, free variables are queried in the currently active dynamic chain, rather than in the static scope chain saved when the function is created.
This will create conflicts. For example, even if Z still exists (as opposed to the previous example of removing variables from the stack), there is still a question: In different function calls, which one does Z value take (from which context and which scope)?
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 the above problems, the concept of closure is introduced.
Closure
A closure is a combination of a code block and data in the context where that code block is created.
Let's look at the following example (pseudocode):
var x = 20;
function foo() {
alert(x); // Free variable "x" == 20
}
// Closure for foo
fooClosure = {
call: foo // Quote to function
lexicalEnvironment: {x: 20} // Search the context of the context
};
In the above example, the "fooClosure" part is pseudo-code. Correspondingly, in ECMAScript, the "foo" function already has an internal property - creating a scope chain of the function context.
"lexical" is usually omitted. In the above example, it is to emphasize that while closure is created, the context data will be saved. The next time the function is called, the free variable can be found in the saved (closure) context. As shown in the above code, the value of the variable "z" is always 10.
The more generalized word we use in the definition - "code block", however, usually (in ECMAScript) uses functions we often use. Of course, not all implementations of closures will bind closures and functions together. For example, in the Ruby language, closures may be: a procedure object, a lambda expression or a code block.
For the implementation of the stack-based implementation to be implemented to still save local variables after being destroyed in context, a stack-based implementation is obviously inapplicable (because it contradicts the stack-based structure). Therefore, in this case, the closure data of the upper scope is implemented by dynamically allocating memory (based on the "heap" implementation), and is used in conjunction with the use of garbage collector (GC for short) and reference counting. This implementation is less performance than stack-based implementations, however, any implementation can always be optimized: it can analyze whether the function uses free variables, functional parameters or functional values, and then decide based on the situation - whether to store the data in the stack or in the heap.
Implementation of ECMAScript closure
After discussing the theory part, let’s introduce how closures are implemented in ECMAScript. It is still necessary to emphasize here again: ECMAScript only uses static (lexical) scopes (while languages such as Perl can use both static scopes and dynamic scopes for variable declarations).
var x = 10;
function foo() {
alert(x);
}
(function (funArg) {
var x = 20;
// The variable "x" is statically saved in the context of (lexical) 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 internal property of the function [[Scope]]. If you don’t understand what [[Scope]] is yet to be understood, it is recommended that you read Chapter 14 first, which provides a very detailed introduction to [[Scope]]. If you fully understand the knowledge of [[Scope]] and scope chain, you will fully understand the closures.
According to the algorithm created by function, we see that in ECMAScript, all functions are closures, because they all save the scope chain of the upper context when they are created (except for exceptions) (regardless of whether this function will be activated in the future - [[Scope]] will be available when the function is created):
var x = 10;
function foo() {
alert(x);
}
// foo is a closure
foo: <FunctionObject> = {
[[Call]]: <code block of foo>,
[[Scope]]: [
global: {
x: 10
}
],
... // Other attributes
};
As we said, for optimization purposes, when a function does not use free variables, the implementation may not be saved in the side-scope chain. However, nothing is said in the ECMA-262-3 specification. Therefore, normally, all parameters are saved in the [[Scope]] attribute during the creation stage.
In some implementations, direct access to the closure scope is allowed. For example, Rhino, for the [[Scope]] attribute of a function, corresponds to a non-standard __parent__ attribute, which was introduced in Chapter 12:
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 a [[Scope]]
It should also be noted here: In ECMAScript, closures created in the same parent context share a [[Scope]] attribute. In other words, a closure changes the variables in [[Scope]] will affect the reading of other closures on their variables:
This means that all internal functions share the same parent scope
var firstClosure;
var secondClosure;
function foo() {
var x = 1;
firstClosure = function () { return ++x; };
secondClosure = function () { return --x; };
x = 2; // Affect AO["x"], in 2 closures public [[Scope]]
alert(firstClosure()); // 3, [[Scope]] through the first closure
}
foo();
alert(firstClosure()); // 4
alert(secondClosure()); // 3
There is a very common misunderstanding about this function. When developers create functions (counting internally) in loop statements, they often fail to get the expected results, and the expectation is that each function has its own value.
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 closures created in the same context share a [[Scope]] attribute. Therefore, the variable "k" in the upper context can be easily changed.
= [
... // Other variable objects
{data: [...], k: 3} // Activity object
];
data[0].[[Scope]] === Scope;
data[1].[[Scope]] === Scope;
data[2].[[Scope]] === Scope;
In this way, when the function is activated, the k finally used has become 3. As shown below, creating a closure can solve this problem:
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = (function _helper(x) {
return function () {
alert(x);
};
})(k); // Pass in the "k" value
}
// The result is correct now
data[0](); // 0
data[1](); // 1
data[2](); // 2
Let's see what happened to the above code? After the function "_helper" is created, it is activated by passing in the parameter "k". Its return value is also a function, which is stored in the corresponding array element. This technique has the following effect: When the function is activated, a new variable object will be created every time "_helper" contains the parameter "x", and the value of "x" is the value of "k" passed in. In this way, the returned function [[Scope]] becomes as follows:
data[0].[[Scope]] === [
... // Other variable objects
Active object AO in parent context: {data: [...], k: 3},
_Helper context active object AO: {x: 0}
];
data[1].[[Scope]] === [
... // Other variable objects
Active object AO in parent context: {data: [...], k: 3},
_Helper context active object AO: {x: 1}
];
data[2].[[Scope]] === [
... // Other variable objects
Active object AO in parent context: {data: [...], k: 3},
_Helper context active object AO: {x: 2}
];
We see that at this time the function's [[Scope]] property has the value we really want. In order to achieve this, we have to create additional variable objects in [[Scope]]. It should be noted that in the returned function, if you want to get the value of "k", then the value will still be 3.
By the way, a lot of articles introducing JavaScript believe that only additional functions are closures, which is wrong. Practice shows that this method is the most effective, however, from a theoretical point of view, all functions in ECMAScript are closures.
However, the above mentioned method is not the only method. The correct value of "k" can also be obtained in other ways, as shown below:
var data = [];
for (var k = 0; k < 3; k++) {
(data[k] = function () {
alert();
}).x = k; // Use k as an attribute of the function
}
// The result is also correct
data[0](); // 0
data[1](); // 1
data[2](); // 2
Funarg and return
Another feature is to 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, such as Ruby, there are many closures in the form of medium, and the corresponding processing of closure return is also different. The following methods are possible: it may be returned directly to the caller, or in some cases - exit directly from the context.
The exit behavior of the ECMAScript standard is as follows:
function getElement() {
[1, 2, 3].forEach(function (element) {
if (element % 2 == 0) {
// Return to function "forEach" function
// instead of returning to the getElement function
alert('found: ' + element); // found: 2
return element;
}
});
return null;
}
However, in ECMAScript, the following effects can be achieved through try catch:
var $break = {};
function getElement() {
try {
[1, 2, 3].forEach(function (element) {
if (element % 2 == 0) {
// // "Return" from getElement
alert('found: ' + element); // found: 2
$ = element;
throw $break;
}
});
} catch (e) {
if (e == $break) {
return $;
}
}
return null;
}
alert(getElement()); // 2
Theoretical version
Here, developers often mistakenly simplify closures to return internal functions from parent context, or even understand that only anonymous functions can be closures.
Let me say that because of the scope chain, all functions are closures (irrelevant to the function type: anonymous functions, FE, NFE, and FD are all closures).
There is only one type of function here, that is, the function created through the Function constructor, because its [[Scope]] only contains global objects.
To better clarify this issue, we give 2 correct version definitions for closures in ECMAScript:
In ECMAScript, closures refer to:
From a theoretical perspective: all functions. Because they are all saved in the upper context data when they are created. This is true even for simple global variables, because accessing global variables in a function is equivalent to accessing free variables, and at this time, the outermost scope is used.
From a practical perspective: The following functions are considered closures:
Even if the context in which it was created was destroyed, it still exists (e.g., the inner function returns from the parent function)
Free variable referenced in code
Closure usage practice
When used in practice, closures can create very elegant designs that allow customization of a variety of calculation methods defined on funarg. The following is an example of array sorting, which accepts a sorting condition function as an argument:
[1, 2, 3].sort(function (a, b) {
... // Sort conditions
});
In the same example, the map method of an array is to map the original array to a new array according to the conditions defined in the function:
[1, 2, 3].map(function (element) {
return element * 2;
}); // [2, 4, 6]
Using functional parameters can easily implement a search method and can support unlimited search conditions:
(function (element) {
return == 'searchCondition';
});
There are also application functions, such as the common forEach method, which applies functions to each array element:
[1, 2, 3].forEach(function (element) {
if (element % 2 != 0) {
alert(element);
}
}); // 1, 3
By the way, the apply and call methods of function objects can also be used as application functions in functional programming. apply and call have been introduced when discussing "this"; here, we regard them as application functions - functions applied to parameters (in apply, parameter list, independent parameters in call):
(function () {
alert([].(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);
There is another very important application for closures - delayed calls:
var a = 10;
setTimeout(function () {
alert(a); // 10, after one second
}, 1000);
There are also callback functions
//...
var x = 10;
// only for example
= function () {
// It will be called when the data is ready;
// Here, no matter what context it is created
// The value of the variable "x" already exists
alert(x); // 10
};
//...
You can also create encapsulated scopes to hide helper objects:
var foo = {};
// Initialization
(function (object) {
var x = 10;
= function _getX() {
return x;
};
})(foo);
alert(()); // 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 closures in ECMAScript. If you have any questions, I will reply to everyone in the comments.
Other references
In this chapter, we will introduce the topic that everyone often discusses in JavaScript - closure. In fact, everyone has already talked about the closure. Nevertheless, we still want to try to discuss closures from a theoretical perspective and see how the closures in ECMAScript work.
As mentioned in the previous article, these articles are series of articles and are related to each other. Therefore, in order to better understand what this article is about to introduce, it is recommended to read Chapter 14 Scope Chain and Chapter 12 Variable Objects first.
Original English: /ecmascript/chapter-6-closures/
Introduction
Before directly discussing ECMAScript closures, it is still necessary to look at some basic definitions in functional programming.
As we all know, in functional languages (ECMAScript also supports this style), functions are data. For example, a function can be assigned to a variable, passed to other functions when parameters, and returned from the function, etc. This type of function has special names and structures.
definition
A functional argument (“Funarg”) — is an argument which value is a function.
Functional parameter ("Funarg") - refers to a parameter whose value is a function.
example:
Copy the codeThe code is as follows:
function exampleFunc(funArg) {
funArg();
}
exampleFunc(function () {
alert('funArg');
});
In the above example, the actual parameters of funarg are actually an anonymous functions passed to exampleFunc.
Conversely, functions that accept functional parameters are called high-order functions (HOF). It can also be called: functional functions or partial mathematical or operators. In the above example, exampleFunc is such a function.
As mentioned earlier, functions can not only be used as parameters, but also as return values. This type of function with function value or function valued functions is called functions with function value.
Copy the codeThe code is as follows:
(function functionValued() {
return function () {
alert('returned function is called');
};
})()();
Functions that can exist in normal data form (for example, when the parameters are passed, accept functional parameters or return as function values) are called first-class functions (generally speaking first-class objects). In ECMAScript, all functions are first-class objects.
Functions can exist as normal data (for example, when passing parameters, accepting functional parameters, or returning as function values) are called first-class functions (usually speaking first-class objects).
In ECMAScript, all functions are first-class objects.
A function that accepts itself as an argument is called an auto-applicative function or self-applicative function:
Copy the codeThe code is as follows:
(function selfApplicative(funArg) {
if (funArg && funArg === selfApplicative) {
alert('self-applicative');
return;
}
selfApplicative(selfApplicative);
})();
Functions that take themselves as the return value are called auto-replicative functions or self-replicative functions. Usually, the word "self-copy" is used in literary works:
Copy the codeThe code is as follows:
(function selfReplicative() {
return selfReplicative;
})();
One of the more interesting patterns of the self-copy function is to accept only one item of the set as a parameter instead of accepting the set itself.
Copy the codeThe code is as follows:
// Functions that accept collections
function registerModes(modes) {
(registerMode, modes);
}
// Usage
registerModes(['roster', 'accounts', 'groups']);
// Declaration of self-copy function
function modes(mode) {
registerMode(mode); // Register a mode
return modes; // Return the function itself
}
// Usage, modes chain call
modes('roster')('accounts')('groups')
//Somewhat similar: ("a").toggle().removClass("b")
However, it is relatively effective and intuitive to use when passing the collection directly.
Variables defined in functional parameters can be accessed when "funarg" is activated (because the variable object that stores context data is created every time it enters the context):
Copy the codeThe code is as follows:
function testFn(funArg) {
// When funarg is activated, the local variable localVar can be accessed
funArg(10); // 20
funArg(20); // 30
}
testFn(function (arg) {
var localVar = 10;
alert(arg + localVar);
});
However, we know from Chapter 14 that in ECMAScript, functions can be encapsulated in parent functions and can use variables in parent function context. This feature will cause funarg problems.
Funarg Problem
In stack-oriented programming languages, local variables of functions are stored on the stack. Whenever the function is activated, these variables and function parameters will be pushed onto the stack.
When the function returns, these parameters are removed from the stack. This model has great limitations on using functions as functional values (for example, returning from the parent function as a return value). In most cases, the problem occurs when the function has free variables.
Free variables refer to variables that are used in functions but are neither function parameters nor local variables of functions.
example:
Copy the codeThe code is as follows:
function testFn() {
var localVar = 10;
function innerFn(innerParam) {
alert(innerParam + localVar);
}
return innerFn;
}
var someFn = testFn();
someFn(20); // 30
In the above example, for the innerFn function, localVar is a free variable.
For systems that use stack-oriented models to store local variables, it means that when the testFn function call is finished, its local variables will be removed from the stack. In this way, an error occurs when a function call is made to innerFn from the outside (because the localVar variable no longer exists).
Moreover, in the above example, in the stack-oriented implementation model, it is impossible to return innerFn with a return value. Because it is also a local variable of the testFn function, it will also be removed as the testFn returns.
Another problem is that when the system adopts dynamic scope and functions are used as function parameters.
See the following example (pseudocode):
Copy the codeThe code is 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 scope, 20 – Using dynamic scope
})();
// It's the same when using foo as a parameter
(function (funArg) {
var z = 30;
funArg(); // 10 – Static scope, 30 – Dynamic scope
})(foo);
We see that systems that adopt dynamic scopes, variables (identifiers) are managed through variable dynamic stacks. Therefore, free variables are queried in the currently active dynamic chain, rather than in the static scope chain saved when the function is created.
This will create conflicts. For example, even if Z still exists (as opposed to the previous example of removing variables from the stack), there is still a question: In different function calls, which one does Z value take (from which context and which scope)?
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 the above problems, the concept of closure is introduced.
Closure
A closure is a combination of a code block and data in the context where that code block is created.
Let's look at the following example (pseudocode):
Copy the codeThe code is as follows:
var x = 20;
function foo() {
alert(x); // Free variable "x" == 20
}
// Closure for foo
fooClosure = {
call: foo // Quote to function
lexicalEnvironment: {x: 20} // Search the context of the context
};
In the above example, the "fooClosure" part is pseudo-code. Correspondingly, in ECMAScript, the "foo" function already has an internal property - creating a scope chain of the function context.
"lexical" is usually omitted. In the above example, it is to emphasize that while closure is created, the context data will be saved. The next time the function is called, the free variable can be found in the saved (closure) context. As shown in the above code, the value of the variable "z" is always 10.
The more generalized word we use in the definition - "code block", however, usually (in ECMAScript) uses functions we often use. Of course, not all implementations of closures will bind closures and functions together. For example, in the Ruby language, closures may be: a procedure object, a lambda expression or a code block.
For the implementation of the stack-based implementation to be implemented to still save local variables after being destroyed in context, a stack-based implementation is obviously inapplicable (because it contradicts the stack-based structure). Therefore, in this case, the closure data of the upper scope is implemented by dynamically allocating memory (based on the "heap" implementation), and is used in conjunction with the use of garbage collector (GC for short) and reference counting. This implementation is less performance than stack-based implementations, however, any implementation can always be optimized: it can analyze whether the function uses free variables, functional parameters or functional values, and then decide based on the situation - whether to store the data in the stack or in the heap.
Implementation of ECMAScript closure
After discussing the theory part, let’s introduce how closures are implemented in ECMAScript. It is still necessary to emphasize here again: ECMAScript only uses static (lexical) scopes (while languages such as Perl can use both static scopes and dynamic scopes for variable declarations).
Copy the codeThe code is as follows:
var x = 10;
function foo() {
alert(x);
}
(function (funArg) {
var x = 20;
// The variable "x" is statically saved in the context of (lexical) 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 internal property of the function [[Scope]]. If you don’t understand what [[Scope]] is yet to be understood, it is recommended that you read Chapter 14 first, which provides a very detailed introduction to [[Scope]]. If you fully understand the knowledge of [[Scope]] and scope chain, you will fully understand the closures.
According to the algorithm created by function, we see that in ECMAScript, all functions are closures, because they all save the scope chain of the upper context when they are created (except for exceptions) (regardless of whether this function will be activated in the future - [[Scope]] will be available when the function is created):
Copy the codeThe code is as follows:
var x = 10;
function foo() {
alert(x);
}
// foo is a closure
foo: <FunctionObject> = {
[[Call]]: <code block of foo>,
[[Scope]]: [
global: {
x: 10
}
],
... // Other attributes
};
As we said, for optimization purposes, when a function does not use free variables, the implementation may not be saved in the side-scope chain. However, nothing is said in the ECMA-262-3 specification. Therefore, normally, all parameters are saved in the [[Scope]] attribute during the creation stage.
In some implementations, direct access to the closure scope is allowed. For example, Rhino, for the [[Scope]] attribute of a function, corresponds to a non-standard __parent__ attribute, which was introduced in Chapter 12:
Copy the codeThe code is 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 a [[Scope]]
It should also be noted here: In ECMAScript, closures created in the same parent context share a [[Scope]] attribute. In other words, a closure changes the variables in [[Scope]] will affect the reading of other closures on their variables:
This means that all internal functions share the same parent scope
Copy the codeThe code is as follows:
var firstClosure;
var secondClosure;
function foo() {
var x = 1;
firstClosure = function () { return ++x; };
secondClosure = function () { return --x; };
x = 2; // Affect AO["x"], in 2 closures public [[Scope]]
alert(firstClosure()); // 3, [[Scope]] through the first closure
}
foo();
alert(firstClosure()); // 4
alert(secondClosure()); // 3
There is a very common misunderstanding about this function. When developers create functions (counting internally) in loop statements, they often fail to get the expected results, and the expectation is that each function has its own value.
Copy the codeThe code is 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 closures created in the same context share a [[Scope]] attribute. Therefore, the variable "k" in the upper context can be easily changed.
Copy the codeThe code is as follows:
= [
... // Other variable objects
{data: [...], k: 3} // Activity object
];
data[0].[[Scope]] === Scope;
data[1].[[Scope]] === Scope;
data[2].[[Scope]] === Scope;
In this way, when the function is activated, the k finally used has become 3. As shown below, creating a closure can solve this problem:
Copy the codeThe code is as follows:
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = (function _helper(x) {
return function () {
alert(x);
};
})(k); // Pass in the "k" value
}
// The result is correct now
data[0](); // 0
data[1](); // 1
data[2](); // 2
Let's see what happened to the above code? After the function "_helper" is created, it is activated by passing in the parameter "k". Its return value is also a function, which is stored in the corresponding array element. This technique has the following effect: When the function is activated, a new variable object will be created every time "_helper" contains the parameter "x", and the value of "x" is the value of "k" passed in. In this way, the returned function [[Scope]] becomes as follows:
Copy the codeThe code is as follows:
data[0].[[Scope]] === [
... // Other variable objects
Active object AO in parent context: {data: [...], k: 3},
_Helper context active object AO: {x: 0}
];
data[1].[[Scope]] === [
... // Other variable objects
Active object AO in parent context: {data: [...], k: 3},
_Helper context active object AO: {x: 1}
];
data[2].[[Scope]] === [
... // Other variable objects
Active object AO in parent context: {data: [...], k: 3},
_Helper context active object AO: {x: 2}
];
We see that at this time the function's [[Scope]] property has the value we really want. In order to achieve this, we have to create additional variable objects in [[Scope]]. It should be noted that in the returned function, if you want to get the value of "k", then the value will still be 3.
By the way, a lot of articles introducing JavaScript believe that only additional functions are closures, which is wrong. Practice shows that this method is the most effective, however, from a theoretical point of view, all functions in ECMAScript are closures.
However, the above mentioned method is not the only method. The correct value of "k" can also be obtained in other ways, as shown below:
Copy the codeThe code is as follows:
var data = [];
for (var k = 0; k < 3; k++) {
(data[k] = function () {
alert();
}).x = k; // Use k as an attribute of the function
}
// The result is also correct
data[0](); // 0
data[1](); // 1
data[2](); // 2
Funarg and return
Another feature is to 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, such as Ruby, there are many closures in the form of medium, and the corresponding processing of closure return is also different. The following methods are possible: it may be returned directly to the caller, or in some cases - exit directly from the context.
The exit behavior of the ECMAScript standard is as follows:
Copy the codeThe code is as follows:
function getElement() {
[1, 2, 3].forEach(function (element) {
if (element % 2 == 0) {
// Return to function "forEach" function
// instead of returning to the getElement function
alert('found: ' + element); // found: 2
return element;
}
});
return null;
}
However, in ECMAScript, the following effects can be achieved through try catch:
Copy the codeThe code is as follows:
var $break = {};
function getElement() {
try {
[1, 2, 3].forEach(function (element) {
if (element % 2 == 0) {
// // "Return" from getElement
alert('found: ' + element); // found: 2
$ = element;
throw $break;
}
});
} catch (e) {
if (e == $break) {
return $;
}
}
return null;
}
alert(getElement()); // 2
Theoretical version
Here, developers often mistakenly simplify closures to return internal functions from parent context, or even understand that only anonymous functions can be closures.
Let me say that because of the scope chain, all functions are closures (irrelevant to the function type: anonymous functions, FE, NFE, and FD are all closures).
There is only one type of function here, that is, the function created through the Function constructor, because its [[Scope]] only contains global objects.
To better clarify this issue, we give 2 correct version definitions for closures in ECMAScript:
In ECMAScript, closures refer to:
From a theoretical perspective: all functions. Because they are all saved in the upper context data when they are created. This is true even for simple global variables, because accessing global variables in a function is equivalent to accessing free variables, and at this time, the outermost scope is used.
From a practical perspective: The following functions are considered closures:
Even if the context in which it was created was destroyed, it still exists (e.g., the inner function returns from the parent function)
Free variable referenced in code
Closure usage practice
When used in practice, closures can create very elegant designs that allow customization of a variety of calculation methods defined on funarg. The following is an example of array sorting, which accepts a sorting condition function as an argument:
Copy the codeThe code is as follows:
[1, 2, 3].sort(function (a, b) {
... // Sort conditions
});
In the same example, the map method of an array is to map the original array to a new array according to the conditions defined in the function:
Copy the codeThe code is as follows:
[1, 2, 3].map(function (element) {
return element * 2;
}); // [2, 4, 6]
Using functional parameters can easily implement a search method and can support unlimited search conditions:
Copy the codeThe code is as follows:
(function (element) {
return == 'searchCondition';
});
There are also application functions, such as the common forEach method, which applies functions to each array element:
Copy the codeThe code is as follows:
[1, 2, 3].forEach(function (element) {
if (element % 2 != 0) {
alert(element);
}
}); // 1, 3
By the way, the apply and call methods of function objects can also be used as application functions in functional programming. apply and call have been introduced when discussing "this"; here, we regard them as application functions - functions applied to parameters (in apply, parameter list, independent parameters in call):
Copy the codeThe code is as follows:
(function () {
alert([].(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);
There is another very important application for closures - delayed calls:
Copy the codeThe code is as follows:
var a = 10;
setTimeout(function () {
alert(a); // 10, after one second
}, 1000);
There are also callback functions
Copy the codeThe code is as follows:
//...
var x = 10;
// only for example
= function () {
// It will be called when the data is ready;
// Here, no matter what context it is created
// The value of the variable "x" already exists
alert(x); // 10
};
//...
You can also create encapsulated scopes to hide helper objects:
Copy the codeThe code is as follows:
var foo = {};
// Initialization
(function (object) {
var x = 10;
= function _getX() {
return x;
};
})(foo);
alert(()); // 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 closures in ECMAScript. If you have any questions, I will reply to everyone in the comments.
Other references
- Javascript Closures (by Richard Cornford)
- Funarg problem
- Closures