JavaScript closures!

Source: Internet
Author: User
Tags try catch

JavaScript Internal principle Series-closures (Closures) 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 that cover the contents of closures, but it's still a matter of trying to talk about closures from a theoretical point of view, and see how the closures inside ECMAScript work.

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); // 20        funArg(20); // 30    }    testFn(function (arg) {        var localVar = 10;        alert(arg + localVar);    });

However, we know that in ECMAScript, a function is a variable that can be encapsulated in a parent function and can use a 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:

    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. The following focus is on the closure of the veil to uncover this.

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:

    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 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 closure    foo: 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.

"Omnipotent" [[Scope]]

It has to be said that the scope chain of JavaScript

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()); // 4    alert(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). This error is also mentioned in the title of the code that the biggest mistake, let us reveal why the button click on the pop-up is 5.

    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 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](); // 0    data[1](); // 1    data[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 (and I almost have the wrong idea, thanks again for the author's point.) )。 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](); // 0    data[1](); // 1    data[2](); // 2

However, Arguments.callee is removed from the ECMAScript (ES5), so this method understands the next line.

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: 2                return element;            }        });        return null;    }    alert(getElement()); // null, 而不是 2

However, the following effect can be achieved with try catch in ECMAScript (This method is fantastic!). ):

    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:
      • 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)
      • 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. Here, we look at them as application functions-functions that are applied to the parameters (the argument list in apply, 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 example    xmlHttpRequestObject.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
Several closure examples

Thanks to JavaScript Closures for Dummies (mirror)

Example 1: A local variable in a closure is a reference, not a copy

    function say667() {        // Local variable that ends up within closure        var num = 666;        var sayAlert = function() {            alert(num);        }        num++;        return sayAlert;    }    var sayAlert = say667();    sayAlert()

So the execution result should pop up 667 instead of 666.

Example 2: Multiple functions bind the same closure because they are defined within the same function.

    function setupSomeGlobals() {        // Local variable that ends up within closure        var num = 666;        // Store some references to functions as global variables        gAlertNumber = function() { alert(num); }        gIncreaseNumber = function() { num++; }        gSetNumber = function(x) { num = x; }    }    setupSomeGlobals();         // 为三个全局变量赋值    gAlertNumber();             //666    gIncreaseNumber();    gAlertNumber();             // 667    gSetNumber(12);             //    gAlertNumber();             //12

Example 3: When assigning functions in a loop, these functions will bind the same closures

    function buildList(list) {        var result = [];        for (var i = 0; i < list.length; i++) {            var item = ‘item‘ + list[i];            result.push( function() {alert(item + ‘ ‘ + list[i])} );        }        return result;    }    function testList() {        var fnList = buildList([1,2,3]);        // using j only to help prevent confusion - could use i        for (var j = 0; j < fnList.length; j++) {            fnList[j]();        }    }

The result of the testlist is to eject the item3 undefined window three times, because these three functions bind the same closure, and the value of item is the result of the last calculation, but when I jumps out of the loop I value is 4, so the result of list[4] is undefined. (Again, the reasons are explained earlier.) )

Example 4: External function All local variables are inside the closure, even if the variable is declared after the intrinsic function definition.

    function sayAlice() {        var sayAlert = function() { alert(alice); }        // Local variable that ends up within closure        var alice = ‘Hello Alice‘;        return sayAlert;    }    var helloAlice=sayAlice();    helloAlice();

The result of the execution is a window that pops up "Hello Alice". Even if a local variable is declared after the function Sayalert, the local variable can still be accessed.

Example 5: Create a new closure each time the function is called

    function newClosure(someNum, someRef) {        // Local variables that end up within closure        var num = someNum;        var anArray = [1,2,3];        var ref = someRef;        return function(x) {            num += x;            anArray.push(num);            alert(‘num: ‘ + num +                ‘\nanArray ‘ + anArray.toString() +                ‘\nref.someVar ‘ + ref.someVar);        }    }    closure1=newClosure(40,{someVar:‘closure 1‘});    closure2=newClosure(1000,{someVar:‘closure 2‘});    closure1(5); // num:45 anArray[1,2,3,45] ref:‘someVar closure1‘    closure2(-10);// num:990 anArray[1,2,3,990] ref:‘someVar closure2‘

Singleton single piece:

    var singleton = function () {        var privateVariable;        function privateFunction(x) {            ...privateVariable...        }        return {            firstMethod: function (a, b) {                ...privateVariable...            },            secondMethod: function (c) {                ...privateFunction()...            }    };

This single piece is implemented by a closure package. The encapsulation of private members and methods is accomplished through closures. The anonymous main function returns an object. The object contains two methods, Method 1 can method private variables, and Method 2 accesses internal private functions. Note that the place where the anonymous main function ends is ' () ' and cannot produce a single piece without this ' () '. Because an anonymous function can only return a unique object, it cannot be called elsewhere. This is the use of closures to produce a single piece of the method.

JavaScript 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.