JavaScript Internal principle Series-closures (Closures)

Source: Internet
Author: User
Tags try catch

Original link: http://www.faceye.net/search/142545.html overview

This article will cover a topic that JavaScript often discusses-closures (closure). Closures are already a commonplace topic, and there are a lot of articles about closures (without losing some very good articles, such as the extended reading of Richard Cornford's article is very good), however, here still try to discuss the closure from a theoretical point of view, See how the closures inside the ECMAScript work.

As mentioned in the previous article, these articles are series of articles that are related to each other. Therefore, in order to better understand the content of this article, it is recommended to read the fourth chapter-scope chain and chapter II-variable object.

Introduction

Before discussing ECMAScript closures, let's introduce some basic definitions of functional programming (independent of the ecma-262-3 standard). However, in order to explain these definitions better, here are some examples of ECMAScript.

As is well known, in functional languages (ECMAScript also supports this style), functions are data. For example, a function can be stored in a variable, it can be passed to other functions, when the return value is returned, and so on. This type of function has a special name and structure.

Defined

The function parameter ("Funarg")--is the argument that the value is a function.

Here's an example:

function exampleFunc(funArg) {funArg();}exampleFunc(function () {alert(‘funArg‘);});

The Funarg argument in the above example is an anonymous function passed to Examplefunc.

Conversely, functions that accept function arguments are called higher-order functions (High-order function Abbreviation: HOF). It can also be called a function function or a partial mathematical term: an operator function. In the above example, Examplefunc is such a function.

Previously mentioned, functions can be used as parameters as well as return values. Such functions as return values of functions are called functions with function values (functions with functional value or function valued functions).

(function functionValued() {return function () {alert(‘returned function is called‘);};})()();

Functions that can exist in the form of normal data (for example, when a parameter is passed, a function parameter is accepted, or returned as a function value) are referred to as the first class of functions (typically, the first class object). In ECMAScript, all functions are the first class of objects.

A function that accepts its own parameters, called self-applied functions (auto-applicative function or self-applicative function):

(function selfApplicative(funArg) {if (funArg && funArg === selfApplicative) {alert(‘self-applicative‘);return;}selfApplicative(selfApplicative);})();

Functions that return values by themselves are called self-replicating functions (auto-replicative function or self-replicative function). In general, the word "self-copying" is used in literary works:

(function selfReplicative() {return selfReplicative;})();

Variables defined in a function parameter are accessible when "Funarg" is activated (because the variable object that stores the context data is created each time it enters the context):

function testFn(funArg) {// 激活funarg, 本地变量localVar可访问funArg(10); // 20funArg(20); // 30}testFn(function (arg) {var localVar = 10;alert(arg + localVar);});

However, we know (especially in the fourth chapter) that in ECMAScript, a function can be encapsulated in a parent function and can use a variable of the parent function context. This feature can cause funarg problems.

Funarg problems

In a stack-oriented programming language, local variables for functions are stored on the stack, and each time the function is activated, the variables and function arguments are stacked onto the stack.

When the function returns, these parameters are removed from the stack. This model has a large limit on the use of functions as function values (for example, return values are returned from the parent function). In most cases, the problem arises when the function has free variables.

A free variable is a variable that is used in a function but is not a function parameter or a local variable of a function.

As shown below:

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, 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 finishes, its local variables are removed from the stack. In this way, an error occurs when a function call is made externally to INNERFN (because the Localvar variable is no longer present).

Furthermore, in the case of the stack implementation model, the above example is not possible to return the INNERFN to the return value at all. Because it is also a local variable of the TESTFN function, it will also be removed as the TESTFN returns.

There is also a function object problem and when the system takes a dynamic scope, the function is used as a function parameter.

See the following example (pseudo code):

var z = 10;function foo() {alert(z);}foo(); // 10 – 静态作用域和动态作用域情况下都是(function () {var z = 20;foo(); // 10 – 静态作用域情况下, 20 – 动态作用域情况下})();// 将foo函数以参数传递情况也是一样的(function (funArg) {var z = 30;funArg(); // 10 – 静态作用域情况下, 30 – 动态作用域情况下})(foo);

We see that with dynamic scopes, variable (identifier) processing is managed through a dynamic stack. Therefore, the free variable is queried in the dynamic chain that is currently active, rather than in the static scope chain that is saved when the function is created.

This creates a conflict. For example, even if z is still present (contrary to the example of removing a variable from the stack), there is still a problem: in a different function call, what is the value of Z (from which context, which scope is queried)?

The two types of funarg problems are described above--depending on whether the function is returned with 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.

Closed Package

Closures are a combination of code blocks and data in the context in which the code block is created.

Let's take a look at the following example (pseudo code):

var x = 20;function foo() {alert(x); // 自由变量 "x" == 20}// foo的闭包fooClosure = {call: foo // 对函数的引用lexicalEnvironment: {x: 20} // 查询自由变量的上下文};

In the above example, 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.

Here "lexical" is self-evident, usually omitted. The above example is to emphasize that the context data will be saved while the closure is being created. The next time the function is called, the free variable can be found in the saved (closed) context, as shown in the code above, 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 often use. Of course, not all implementations of closures will tie closures and functions together, for example, in the Ruby language, closures are likely to be: A Program object (procedure object), a lambda expression, or a block of code.

The stack-based implementation is obviously not applicable (because it contradicts the stack-based structure) for the implementation to persist the local variables after the context is destroyed. Therefore, in this case, the closure data for the upper scope is implemented by dynamically allocating memory (based on the "heap" implementation), with the garbage collector (garbage collector referred to as GC) and the reference count (reference counting). This implementation is less performance than a stack-based implementation, however, any implementation can always be optimized: You can analyze whether a function uses free variables, functional arguments, or functional values, and then decide depending on the situation-whether the data is stored on the stack or in the heap.

Implementation of ECMAScript closure package

After discussing the theoretical part, let's introduce how the closure of the ECMAScript is implemented. There is still a need to emphasize again: ECMAScript uses only static (lexical) scopes (and languages such as Perl, which can use either static scopes or dynamic scopes for variable declarations).

var x = 10;function foo() {alert(x);}(function (funArg) {var x = 20;// funArg的变量 "x" 是静态保存的,在该函数创建的时候就保存了funArg(); // 10, 而不是 20})(foo);

From a technical standpoint, the data that creates the upper context of the function is stored in the function's internal properties [[Scope]]. If you do not yet understand what is [[scope]], it is recommended that you read the fourth chapter, which gives a very detailed description of [[scope]]. If you fully understand the [scope] and scope chain knowledge, then the closure is fully understood.

According to the algorithm created by the function, we see that in ECMAScript, all functions are closures, because they are created to preserve the scope chain of the upper context (except in the case of exceptions) (regardless of whether the function will subsequently be activated--[[scope]) when the function is created:

var x = 10;function foo() {alert(x);}// foo is a closurefoo: <FunctionObject> = {[[Call]]: <code block of foo>,[[Scope]]: [global: {x: 10}],... // other properties};

As mentioned earlier, for optimization purposes, when a function does not use a free variable, the implementation layer may not save the upper-scope chain. However, there is no explanation for this in the ecmascript-262-3 standard, so strictly speaking-all functions will save the upper-scope chain in [[Scope]] at the time of creation.

In some implementations, access to the scope of the closure is allowed directly. For example, rhino, for a function's [[Scope]] property, should have a non-standard parent property, as described in Chapter Two:

var global = this;var x = 10;var foo = (function () {var y = 20;return function () {alert(y);};})();foo(); // 20alert(foo.__parent__.y); // 20foo.__parent__.y = 30;foo(); // 30// 还可以操作作用域链alert(foo.__parent__.__parent__ === global); // truealert(foo.__parent__.__parent__.x); // 10
"Omnipotent" [[Scope]]

Also note here: In ECMAScript, closures created in the same context are shared with a [[Scope]] property. That is, a closure that modifies variables in it affects the reading of its variables by other closures:

var firstClosure;var secondClosure;function foo() {var x = 1;firstClosure = function () { return ++x; };secondClosure = function () { return --x; };x = 2; // 对AO["x"]产生了影响, 其值在两个闭包的[[Scope]]中alert(firstClosure()); // 3, 通过 firstClosure.[[Scope]]}foo();alert(firstClosure()); // 4alert(secondClosure()); // 3

Because of this feature, many people make a very common mistake: when a function is created in a loop, and then the index value of the loop is bound to each function, the result is usually not expected (it is expected that each function will be able to get its corresponding index value).

var data = [];for (var k = 0; k < 3; k++) {data[k] = function () {alert(k);};}data[0](); // 3, 而不是 0data[1](); // 3, 而不是 1data[2](); // 3, 而不是 2

The above example proves that a closure created in the same context is a common [[Scope]] property. Therefore, the variable "K" in the upper context can easily be changed.

As shown below:

activeContext.Scope = [... // higher variable objects{data: [...], k: 3} // activation object];data[0].[[Scope]] === Scope;data[1].[[Scope]] === Scope;data[2].[[Scope]] === Scope;

In this way, when the function is activated, the end-use K becomes 3.

As shown below, creating an additional 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](); // 0data[1](); // 1data[2](); // 2

In the above example, after the function "_helper" is created, it is activated by the parameter "K". Its return value is also a function, which is stored in the corresponding array element. This technique produces the following effect: When the function is activated, each "_helper" creates a new variable object, which contains the parameter "x", and the value of "X" is the value of the "K" passed in. In this way, the [[Scope]] of the returned function is 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 point the [[scope]] property of the function has the desired value, and for this purpose we have to create an additional variable object in [[scope]]. Note that in the returned function, if you want to get the value of "K", the value will still be 3.

By the way, a lot of the articles about JavaScript think that only the extra function is the closure, which is wrong. In practice, this approach is most effective, however, from a theoretical standpoint, all functions in ECMAScript are closed.

However, the method mentioned above is not the only method. The correct value of "K" can also be obtained by other means, as follows:

var data = [];for (var k = 0; k < 3; k++) {(data[k] = function () {alert(arguments.callee.x);}).x = k; // 将“k”存储为函数的一个属性}// 同样也是可行的data[0](); // 0data[1](); // 1data[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, such as Ruby, there are many forms of closures, and the corresponding processing closure returns are different, the following are possible: it may be returned directly to the caller, or in some cases, directly out of context.

The exit behavior of the ECMAScript standard is as follows:

function getElement() {[1, 2, 3].forEach(function (element) {if (element % 2 == 0) {// 返回给函数"forEach",// 而不会从getElement函数返回alert(‘found: ‘ + element); // found: 2return element;}});return null;}alert(getElement()); // null, 而不是 2

However, the following effect can be achieved with try catch in ECMAScript:

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

Often, programmers mistakenly think that only anonymous functions are closures. This is not the case, as we have seen-it is because of the scope chain that all functions are closed (regardless of function type: Anonymous functions, FE,NFE,FD are closures), except for a class of functions, which are functions created through the function constructor because [[Scope] ] contains only global objects. To better clarify the problem, we have two definitions (i.e. two closures) for closures in ECMAScript:

In ECMAScript, closures refer to:

    • From a theoretical point of view: all functions. Because they are all created, the data in the upper context is saved. This is true even for simple global variables, because accessing a global variable in a function is equivalent to accessing a free variable, at which point the outermost scope is used.
    • From a practical point of view: The following functions are considered closures:
    1. Even if the context in which it was created has been destroyed, it still exists (for example, an intrinsic function is returned from the parent function)
    2. A free variable is referenced in the code
Closures Practice

In practice, closures can create a very elegant design that allows customization of the various calculations defined on the Funarg. The following is an example of array ordering, which takes a sort condition function as an argument:

[1, 2, 3].sort(function (a, b) {... // 排序条件});

In the same example, the array map method (not all implementations support the array map method, SpiderMonkey is supported from version 1.6), which maps the original array to a new array based on the conditions defined in the function:

[1, 2, 3].map(function (element) {return element * 2;}); // [2, 4, 6]

Using functional parameters, it is convenient to implement a search method, and can support an infinite number of search conditions:

someCollection.find(function (element) {return element.someProperty == ‘searchCondition‘;});

There are also application functions, such as the common foreach method, that apply funarg 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 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"; here, we look at them as application functions--functions applied to parameters (in Apply is a parameter list, which is a separate argument in call):

(function () {alert([].join.call(arguments, ‘;‘)); // 1;2;3}).apply(this, [1, 2, 3]);

Closures also have another very important application-deferred invocation:

var a = 10;setTimeout(function () {alert(a); // 10, 一秒钟后}, 1000);

It can also be used for callback functions:

...var x = 10;// only for examplexmlHttpRequestObject.onreadystatechange = function () {// 当数据就绪的时候,才会调用;// 这里,不论是在哪个上下文中创建,变量“x”的值已经存在了alert(x); // 10};..

You can also use it to encapsulate scopes to hide the helper objects:

    var foo = {};// initialization(function (object) {var x = 10;object.getX = function _getX() {return x;};})(foo);alert(foo.getX()); // get closured "x" – 10
Summarize

Original link:

Http://www.faceye.net/search/142545.html

This article introduces more theoretical knowledge about ecmascript-262-3, and I think that these basic theories help to understand the concept of closure in ECMAScript

JavaScript Internal principle Series-closures (Closures)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.