Deep understanding of JavaScript series (16) Closures (Closures)

Source: Internet
Author: User
Tags try catch

Introduction
In this chapter, we will introduce closure, a common topic in JavaScript ). In fact, we have all talked about closures. However, we should try to discuss the closure from a theoretical point of view to see how the closure in ECMAScript actually works.

As mentioned in the previous article, these articles are a series of articles that are related to each other. Therefore, to better understand the content described in this article, we recommend that you read chapter 1 Scope chain and Chapter 14th variable objects first.

Http://dmitrysoshnikov.com/ecmascript/chapter-6-closures/.
Overview
Before directly discussing the ECMAScript closure, it is necessary to take a 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 a value to a variable. When a parameter is passed to another function, it can also be returned from the function. Such functions have special names and structures.

Definition
A functional argument ("Funarg")-is an argument which value is a function.
Function parameter ("Funarg") -- refers to the parameter whose value is a function.
Example:Copy codeThe Code is as follows: function exampleFunc (funArg ){
FunArg ();
}

ExampleFunc (function (){
Alert ('funarg ');
});

In the above example, the actual parameter funarg is actually an anonymous function passed to exampleFunc.

In turn, a function that accepts a function-type parameter is called a high-order function (HOF ). It can also be called a function or partial mathematical or operator. In the above example, exampleFunc is such a function.

Previously mentioned, a function can be used not only as a parameter but also as a return value. These functions return values are called functions with function values (functions with functional value or function valued functions ).Copy codeThe Code is as follows: (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-type parameter is accepted or returned with a function value) are called first-class functions (generally the first-class objects ). In ECMAScript, all functions are the first class objects.

A function can exist as a normal data (for example, when a parameter is passed, a function-type parameter is accepted or returned as a function value ).

In ECMAScript, all functions are the first class objects.

A function that accepts itself as a parameter is called an auto-applicative function or self-applicative function ):Copy codeThe Code is as follows: (function selfApplicative (funArg ){

If (funArg & funArg === selfApplicative ){
Alert ('self-applicative ');
Return;
}

SelfApplicative (selfApplicative );

})();

Auto-replicative function or self-replicative function ). Generally, the word "self-replication" is used in literature:Copy codeThe Code is as follows: (function selfReplicative (){
Return selfReplicative;
})();

One of the more interesting modes of the auto-copy function is to accept only one item of the set as a parameter, instead of accepting the set itself.Copy codeThe Code is as follows: // receives the function of the set.
Function registerModes (modes ){
Modes. forEach (registerMode, modes );
}

// Usage
RegisterModes (['roster', 'accounts', 'groups']);

// Self-replication function declaration
Function modes (mode ){
RegisterMode (mode); // register a mode
Return modes; // return the function itself
}

// Usage, modes chained call
Modes ('roster') ('accounts') ('groups ')

// A bit similar: jQueryObject. addClass ("a"). toggle (). removClass ("B ")

However, directly transferring a set is relatively effective and intuitive.

Variables defined in functional parameters can be accessed when "funarg" is activated (because the variable object storing context data is created every time it enters the context ):Copy 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 14th that in ECMAScript, a function can be encapsulated in a parent function and can use variables in the context of the parent function. This feature causes funarg problems.

Funarg Problems
In a stack-oriented programming language, Partial Variables of a function are stored on the stack. Each time a function is activated, these variables and function parameters are pushed onto the stack.

When the function returns, these parameters are removed from the stack. This model imposes great restrictions on using a function as a function type value (for example, returning a function as a return value from the parent function ). In most cases, the problem occurs when the function has a free variable.

A free variable is a variable used in a function. It is neither a function parameter nor a local variable of the function.

Example:Copy 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, localVar is a free variable for the innerFn function.

For systems that use the stack-oriented model to store local variables, this means that when the testFn function call ends, its local variables will be removed from the stack. In this way, when the innerFn function is called from outside, an error occurs (because the localVar variable does not exist ).

Moreover, in the stack-oriented implementation model, it is impossible to return innerFn with a returned value. Because it is also a local variable of the testFn function, it will be removed with the return of testFn.

Another problem is when the system uses dynamic scopes and functions as function parameters.

Take the following example (pseudo code ):Copy codeThe Code is as follows: var z = 10;

Function foo (){
Alert (z );
}

Foo (); // 10-When static and dynamic scopes are used

(Function (){

Var z = 20;
Foo (); // 10-use static scope, 20-use dynamic scope

})();

// Use foo as a parameter.
(Function (funArg ){

Var z = 30;
FunArg (); // 10-static scope, 30-dynamic scope

}) (Foo );

We can see that the system using dynamic scopes and variables (identifiers) are managed through the dynamic stack of variables. Therefore, free variables are queried in the current active dynamic chain, rather than in the static scope chain saved during function creation.

In this way, a conflict occurs. For example, even if Z still exists (opposite to the previous example of removing a variable from the stack), there is still a problem: in different function calls, which of the following is the value of Z (from which context and 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 ).

To solve the above problem, the concept of closure is introduced.

Closure
Closure is the combination of a code block and Context data that creates the code block.
Let's take a look at the following example (pseudo code ):Copy codeCode: var x = 20;

Function foo (){
Alert (x); // free variable "x" = 20
}

// It is the foo closure.
FooClosure = {
Call: foo // reference to function
LexicalEnvironment: {x: 20} // search the context
};

In the preceding example, "fooClosure" is a pseudo-code. Correspondingly, in ECMAScript, the "foo" function already has an internal Attribute-create the scope chain of the context of the function.

"Lexical" is usually omitted. In the preceding example, Context data is saved when the closure is created. When the function is called next time, the free variable can be found in the saved (closure) context. As shown in the code above, the value of the variable "z" is always 10.

In the definition, we use the generalized word "code block". However, we usually use functions that we often use (in ECMAScript. Of course, not all implementations of the closure will bind the closure with the function. For example, in Ruby, the closure may be a procedure object ), A lambda expression or code block.

Stack-based implementation is obviously not applicable to the implementation of saving local variables after the context is destroyed (because it is in conflict with the stack-based structure ). Therefore, in this case, the closure data in the upper-level scope is implemented by dynamically allocating memory (based on the implementation of "heap"), and the garbage collector (GC) is used together) and reference counting ). This implementation method has lower performance than stack-based implementation. However, any implementation can always be optimized: You can analyze whether a function uses free variables, function parameters, or function values, then decide whether to store data in the stack or heap based on the situation.

Implementation of ECMAScript Closure
After discussing the theory, let's introduce how closure is implemented in ECMAScript. It is necessary to emphasize that ECMAScript only uses static (lexical) scopes (while languages such as Perl can use static scopes or dynamic scopes for variable Declaration ).Copy codeCode: var x = 10;

Function foo (){
Alert (x );
}

(Function (funArg ){

Var x = 20;

// The variable "x" is statically saved in the (lexical) context. It is saved when the function is created.
FunArg (); // 10 instead of 20

}) (Foo );

Technically, the data in the parent context of the function is stored in the internal attribute [Scope] of the function. If you do not know what [Scope] is, we recommend that you read chapter 1, which describes [Scope] in detail. If you fully understand the [[Scope] and Scope chain knowledge, then you fully understand the closure.

According to the algorithm created by the function, we can see that in ECMAScript, all functions are closures, because they all save the scope chain of the Upper-layer context when they are created (except for exceptions) (whether or not the function will be activated later -- [[Scope] will be available when the function is created ):Copy codeCode: 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 the purpose of optimization, when a function does not use free variables, the implementation may not be saved in the sub-scope chain. However, nothing in the ECMA-262-3 specification. Therefore, normally, all parameters are stored in the [[Scope] attribute during creation.

In some implementations, direct access to the closure scope is allowed. For example, for the [Scope] attribute of a function, Rhino has a non-standard _ parent _ attribute, which is described in Chapter 12th:Copy 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

// You can use the scope chain to move to the top
Alert (foo. _ parent _. _ parent _ = global); // true
Alert (foo. _ parent _. _ parent _. x); // 10

All objects reference A [Scope]
Note that in ECMAScript, the closures created in the same parent context share a [Scope] attribute. That is to say, if a closure modifies the variable [[Scope], it will affect the reading of the variable by other closures:

This means that all internal functions share the same parent scope.Copy codeThe Code is as follows: var firstClosure;
Var secondClosure;

Function foo (){

Var x = 1;

FirstClosure = function () {return ++ x ;};
SecondClosure = function () {return -- x ;};

X = 2; // affects AO ["x"], which is in two [Scope]

Alert (firstClosure (); // 3, through the [[Scope] of the first closure
}

Foo ();

Alert (firstClosure (); // 4
Alert (secondClosure (); // 3

There is a common misunderstanding about this function. Developers often fail to get the expected results when creating a function (counting internally) in a loop statement, it is expected that each function has its own value.Copy codeThe Code is as follows: var data = [];

For (var k = 0; k <3; k ++ ){
Data [k] = function (){
Alert (k );
};
}

Data [0] (); // 3, instead of 0
Data [1] (); // 3, instead of 1
Data [2] (); // 3, instead of 2

The above example proves that the closure created in the same context shares a [[Scope] attribute. Therefore, the variable "k" in the upper context can be easily changed.Copy codeThe Code is as follows: activeContext. Scope = [
... // 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 final k used is changed to 3. As shown in the following figure, creating a closure can solve this problem:Copy codeThe Code is as follows: var data = [];

For (var k = 0; k <3; k ++ ){
Data [k] = (function _ helper (x ){
Return function (){
Alert (x );
};
}) (K); // input the "k" Value
}

// Now the result is correct
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 "k" parameter. The returned value is also a function, which is saved in the corresponding array element. This technique produces the following results: When a function is activated, a new variable object will be created each time "_ helper" contains the parameter "x ", the value of "x" is the value of "k" passed in. In this way, the [[Scope] of the returned function is as follows:Copy codeThe Code is as follows: data [0]. [[Scope] = [
... // Other variable objects
Activity object AO: {data: [...], k: 3} in parent context },
_ Activity object AO: {x: 0} in the helper Context}
];

Data [1]. [[Scope] = [
... // Other variable objects
Activity object AO: {data: [...], k: 3} in parent context },
_ Activity object AO: {x: 1} in the helper Context}
];

Data [2]. [[Scope] = [
... // Other variable objects
Activity object AO: {data: [...], k: 3} in parent context },
_ Activity object AO: {x: 2} in the helper Context}
];

We can see that the [[Scope] attribute of the function has the desired value. To achieve this purpose, we have to create additional variable objects in [[Scope. Note that, in the returned function, if you want to obtain the value of "k", the value will still be 3.

By the way, many articles about JavaScript believe that only the newly created function is the closure, which is wrong. In practice, this method is the most effective. However, theoretically, all functions in ECMAScript are closures.

However, the method mentioned above is not the only method. You can obtain the correct "k" value in other ways, as shown below:Copy codeThe Code is as follows: var data = [];

For (var k = 0; k <3; k ++ ){
(Data [k] = function (){
Alert (arguments. callee. x );
}). X = k; // use k as an attribute of the function.
}

// The result is correct.
Data [0] (); // 0
Data [1] (); // 1
Data [2] (); // 2

Funarg and return
Another feature is returned from the closure. In ECMAScript, the Return Statement in the closure returns the control flow to the call context (caller ). In other languages, for example, Ruby, there are many closure forms, and the corresponding processing closure returns are also different. The following methods are possible: they may be directly returned to the caller, or in some cases -- exit directly from the context.

The exit behavior of ECMAScript is as follows:Copy codeThe Code is as follows: function getElement (){

[1, 2, 3]. forEach (function (element ){

If (element % 2 = 0 ){
// Return to the function "forEach"
// Instead of returning it to the getElement Function
Alert ('found: '+ element); // found: 2
Return element;
}

});

Return null;
}

However, in ECMAScript, try catch can achieve the following effects:Copy codeCode: var $ break = {};

Function getElement (){

Try {

[1, 2, 3]. forEach (function (element ){

If (element % 2 = 0 ){
//// "Return" 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 that developers often mistakenly interpret the closure as returning internal functions from the parent context, or even as that only anonymous functions can be closures.

Besides, because of the scope chain, all functions are closures (not related to function types: anonymous functions, FE, NFE, and FD are closures ).
Only one type of Function exists, that is, the Function created through the Function constructor, because its [[Scope] only contains global objects.

To better clarify this issue, we provide two correct version definitions for the closure in ECMAScript:

In ECMAScript, the closure refers:

Theoretically: All functions. Because they all save the upper-layer 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. In this case, the outermost scope is used.
From a practical perspective: The following functions are regarded as closures:
Even if the context for creating it has been destroyed, it still exists (for example, the internal function returns from the parent function)
A free variable is referenced in the code.
Closure usage practice
In actual use, the closure can create a very elegant design that allows you to customize multiple computing methods defined on funarg. The following is an example of array sorting. It accepts a sorting condition function as a parameter:Copy codeThe Code is as follows: [1, 2, 3]. sort (function (a, B ){
... // Sorting Condition
});

In the same example, the array map method maps the original array to a new array based on the conditions defined in the function:Copy codeThe Code is as follows: [1, 2, 3]. map (function (element ){
Return element * 2;
}); // [2, 4, 6]

Using function parameters, you can easily implement a search method and support unlimited search conditions:Copy codeThe Code is as follows: someCollection. find (function (element ){
Return element. someProperty = 'searchcondition ';
});

There are also application functions, such as the common forEach method, which applies the function to each array element:Copy 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. The apply and call methods have already been introduced when we talk about "this". Here, we regard them as application functions -- functions applied to parameters (the parameter list is in apply, independent parameters in call ):Copy codeThe Code is as follows: (function (){
Alert ([]. join. call (arguments, ';'); // 1; 2; 3
}). Apply (this, [1, 2, 3]);

The closure also has another very important application-delayed calling:Copy codeCode: var a = 10;
SetTimeout (function (){
Alert (a); // 10, after one second
},1000 );

There are also callback FunctionsCopy codeThe Code is as follows ://...
Var x = 10;
// Only for example
XmlHttpRequestObject. onreadystatechange = function (){
// Called only when data is ready;
// Here, no matter in which context you create
// The value of the variable "x" already exists.
Alert (x); // 10
};
//...

You can also create an encapsulated scope to hide the secondary object:Copy codeThe Code is as follows: var foo = {};

// Initialization
(Function (object ){

Var x = 10;

Object. getX = function _ getX (){
Return x;
};

}) (Foo );

Alert (foo. getX (); // obtain the closure "x"-10

Summary
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 references

  • Javascript Closures (by Richard Cornford)
  • Funarg problem
  • 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.