After discussing the theoretical part, let us introduce how closures are implemented in ECMAScript. It’s worth emphasizing again here: ECMAScript only uses static (lexical) scope (while languages such as Perl can use both static and dynamic scopes for variable declarations).
var x = 10; function foo() { alert(x); } (function (funArg) { var x = 20; // 变量"x"在(lexical)上下文中静态保存的,在该函数创建的时候就保存了 funArg(); // 10, 而不是20 })(foo);
Technically speaking, the data of the parent context that created the function is stored in the function's internal property [[Scope]]. If you don't know what [[Scope]] is, it is recommended that you read the previous chapter first, which gives a very detailed introduction to [[Scope]]. If you fully understand [[Scope]] and scope chain knowledge, then you will also fully understand closures.
According to the function creation algorithm, we see that in ECMAScript, all functions are closures, because they save the scope chain of the upper context when they are created (except for exceptions) (regardless of this function Whether it will be activated later - [[Scope]] is available when the function is created):
var x = 10; function foo() { alert(x); } // foo是闭包 foo: <FunctionObject> = { [[Call]]: <code block of foo>, [[Scope]]: [ global: { x: 10 } ], ... // 其它属性 };
As we said, for optimization purposes, when a function does not use free variables, the implementation may not be saved in the side effect domain chain inside. However, nothing is said in the ECMA-262-3 specification. Therefore, normally, all parameters are saved in the [[Scope]] attribute during the creation phase.
Some implementations allow direct access to the closure scope. For example, Rhino, for the [[Scope]] attribute of the function, there is a non-standard __parent__ attribute:
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 // 可以通过作用域链移动到顶部 alert(foo.__parent__.__parent__ === global); // true alert(foo.__parent__.__parent__.x); // 10
All objects refer to a [[Scope]]
Also note here: in ECMAScript, Closures created in the same parent context share a [[Scope]] attribute. In other words, a closure's modification of the variables in [[Scope]] will affect other closures' reading of its variables:
var firstClosure; var secondClosure; function foo() { var x = 1; firstClosure = function () { return ++x; }; secondClosure = function () { return --x; }; x = 2; // 影响 AO["x"], 在2个闭包公有的[[Scope]]中 alert(firstClosure()); // 3, 通过第一个闭包的[[Scope]] } foo(); alert(firstClosure()); // 4 alert(secondClosure()); // 3
This means: all internal functions share the same parent scope
There is a very common misunderstanding about this function. Developers often do not get the expected results when creating functions in loop statements (counting internally), 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, 而不是0 data[1](); // 3, 而不是1 data[2](); // 3, 而不是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.
activeContext.Scope = [ ... // 其它变量对象 {data: [...], k: 3} // 活动对象 ]; data[0].[[Scope]] === Scope; data[1].[[Scope]] === Scope; data[2].[[Scope]] === Scope;
In this way, when the function is activated, the final k 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); // 传入"k"值 } // 现在结果是正确的了 data[0](); // 0 data[1](); // 1 data[2](); // 2
Let’s see what happens in 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 produces the following effects: When the function is activated, each time "_helper" creates a new variable object, which contains the parameter "x", and the value of "x" is the value of "k" passed in. In this way, the [[Scope]] of the returned function becomes as follows:
data[0].[[Scope]] === [ ... // 其它变量对象 父级上下文中的活动对象AO: {data: [...], k: 3}, _helper上下文中的活动对象AO: {x: 0} ]; data[1].[[Scope]] === [ ... // 其它变量对象 父级上下文中的活动对象AO: {data: [...], k: 3}, _helper上下文中的活动对象AO: {x: 1} ]; data[2].[[Scope]] === [ ... // 其它变量对象 父级上下文中的活动对象AO: {data: [...], k: 3}, _helper上下文中的活动对象AO: {x: 2} ];
We see that at this time the [[Scope]] attribute of the function has the really desired value. In order to achieve this For this purpose, 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", the value will still be 3.
By the way, a large number of articles introducing JavaScript believe that only additionally created functions are closures, which is wrong. In practice, this method is the most effective. However, from a theoretical point of view, all functions in ECMAScript are closures.
However, the methods mentioned above are not the only ones. The correct value of "k" can also be obtained in other ways, as follows:
var data = []; for (var k = 0; k < 3; k++) { (data[k] = function () { alert(arguments.callee.x); }).x = k; // 将k作为函数的一个属性 } // 结果也是对的 data[0](); // 0 data[1](); // 1 data[2](); // 2
Funarg and return
Another feature is returning from a closure. In ECMAScript, a return statement in a closure returns control flow to the calling context (the caller). In other languages, such as Ruby, there are many forms of closures, and the corresponding closure returns are also different. The following methods are possible: it may be returned directly to the caller, or in some cases --Exit directly from the context.
ECMAScript standard exit behavior is as follows:
function getElement() { [1, 2, 3].forEach(function (element) { if (element % 2 == 0) { // 返回给函数"forEach"函数 // 而不是返回给getElement函数 alert('found: ' + element); // found: 2 return element; } }); return null; }
However, in ECMAScript, the following effect can be achieved through try catch:
var $break = {}; function getElement() { try { [1, 2, 3].forEach(function (element) { if (element % 2 == 0) { // // 从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
To explain here, developers often mistakenly simplify closures and understand them from the parent context Returning internal functions even understands that only anonymous functions can be closures.
Again, because of the scope chain, all functions are closures (regardless of function type: anonymous functions, FE, NFE, and FD are all closures).
There is only one type of function except that, that is the function created through the Function constructor, because its [[Scope]] only contains global objects. In order to better clarify this issue, we give two correct version definitions of closures in ECMAScript:
In ECMAScript, closures refer to:
From a theoretical perspective: all functions. Because they all save the data of the upper context 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. 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 has been destroyed, it still exists (for example, the inner function returns from the parent function)
Free variables are referenced in the code
That’s it JavaScript Closure Part 2: The content of closure implementation. For more related content, please pay attention to the PHP Chinese website (www.php.cn)!