Home  >  Article  >  Web Front-end  >  In-depth understanding of JavaScript series (16) Closures_javascript skills

In-depth understanding of JavaScript series (16) Closures_javascript skills

WBOY
WBOYOriginal
2016-05-16 17:54:241143browse

Introduction
In this chapter we will introduce a topic that is often discussed in JavaScript - closure. In fact, everyone has already talked about closures. Nonetheless, here we will try to discuss closures from a theoretical perspective and see how closures in ECMAScript actually work internally.

As mentioned in the previous article, these articles are a series of articles and are related to each other. Therefore, in order to better understand the content to be introduced in this article, it is recommended to read Chapter 14 Scope Chain and Chapter 12 Variable Objects first.

English original text: http://dmitrysoshnikov.com/ecmascript/chapter-6-closures/
Introduction
Before discussing ECMAScript closures directly, it is still necessary to take a look at functional programming Some basic definitions.

As we all know, in functional languages ​​(ECMAScript also supports this style), functions are data. For example, functions can be assigned to variables, passed as parameters to other functions, returned from functions, etc. Such functions have special names and structures.

Definition
A functional argument (“Funarg”) — is an argument which value is a function.
A functional argument (“Funarg”) — is an argument whose value is a function.
Example:

Copy code The code is as follows:

function exampleFunc(funArg) {
funArg();
}

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

In the above example The actual parameter of funarg is actually the anonymous function passed to exampleFunc.

Conversely, a function that accepts functional parameters is called a high-order function (HOF for short). It can also be called: functional function or partial mathematical theory or operator. In the above example, exampleFunc is such a function.

As mentioned before, functions can not only be used as parameters, but also as return values. Such functions that return a function are called functions with functional value or function valued functions.
Copy code The code is as follows:

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

A function that can exist in the form of normal data (for example : When parameters are passed, accept functional parameters or return function values), they are called first-class functions (generally speaking, first-class objects). In ECMAScript, all functions are first-class objects.

Functions that can exist as normal data (for example, when parameters are passed, accept functional parameters or return function values) are called first-class functions (generally speaking, first-class objects).

In ECMAScript, all functions are first-class objects.

A function that accepts itself as a parameter is called an auto-applicative function or self-applicative function:
Copy code The code is as follows:

(function selfApplicative(funArg) {

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

selfApplicative(selfApplicative);

})();

with self as the return value The function is called an auto-replicative function or self-replicative function. Usually, the term "self-replicating" is used in literature:
Copy code The code is as follows:

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

One of the more interesting patterns for self-replicating functions is to accept only one item of a collection as Parameters are accepted instead of accepting the collection itself.
Copy code The code is as follows:

// Function that accepts a collection
function registerModes( modes) {
modes.forEach(registerMode, modes);
}

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

//Declaration of self-copying 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: jQueryObject.addClass("a") .toggle().removClass("b")

But passing the collection directly 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 each time the context is entered):
Copy code The code is as follows:

function testFn(funArg) {
//When funarg is activated, local The 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 from the parent function context. This feature can cause funarg problems.

Funarg problem
In a stack-oriented programming language, the local variables of a function 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 will be removed from the stack. This model places significant restrictions on using functions as functional values ​​(for example, as return values ​​from parent functions). Most of the time, problems arise when functions have free variables.

Free variables refer to variables that are used in a function but are neither function parameters nor local variables of the function

Example:
Copy code The 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 a 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 function call to innerFn is made from the outside, an error will occur (because the localVar variable no longer exists).

Moreover, in the above example, in the stack-oriented implementation model, it is simply impossible to return innerFn as a return value. Because it is also a local variable of the testFn function, it will also be removed when testFn returns.

Another problem is when the system uses dynamic scope and functions are used as function parameters.

Look at the following example (pseudocode):
Copy the code The code is as follows:

var z = 10;

function foo() {
alert(z);
}

foo(); // 10 – Using static and dynamic scopes When

(function () {

var z = 20;
foo(); // 10 – use static scope, 20 – use dynamic scope

})();

// It’s the same when foo is used as a parameter
(function (funArg) {

var z = 30;
funArg() ; // 10 – static scope, 30 – dynamic scope

})(foo);

We see that using dynamic scope, variables (identifiers) The system is managed through a dynamic stack of variables. Therefore, free variables are queried in the currently active dynamic chain, rather than in the static scope chain that was saved when the function was created.

This will cause conflict. For example, even if Z still exists (as opposed to the previous example of removing the variable from the stack), there is still a question: which value of Z takes on different function calls (from which context, which scope) query)?

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.

Closures
A closure is the combination of a block of code and the data in the context in which the block of code was created.
Let’s look at the following example (pseudocode):
Copy the code The code is as follows:

var x = 20;

function foo() {
alert(x); // Free variable "x" == 20
}

// is foo closure
fooClosure = {
call: foo // Reference to function
lexicalEnvironment: {x: 20} // Search context's context
};

In the above example, the "fooClosure" part is pseudocode. Correspondingly, in ECMAScript, the "foo" function already has an internal property - the scope chain that creates the context of the function.

"lexical" is usually omitted. The above example is to emphasize that when the 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, and as shown in the above code, the value of the variable "z" will always be 10.

We use the broader term "code block" in the definition, however, usually (in ECMAScript) we use functions that we often use. Of course, not all implementations of closures will tie closures and functions together. For example, in Ruby language, closures may be: a procedure object, a lambda expression or code block.

For the purpose of saving local variables after the context is destroyed, the stack-based implementation is obviously not applicable (because it conflicts with the stack-based structure). Therefore, in this case, the closure data of the upper scope is implemented by dynamically allocating memory (based on "heap" implementation), combined with the use of garbage collector (garbage collector referred to as GC) and reference counting (reference counting). ). This implementation is less performant than a stack-based implementation, however, any implementation can always be optimized: you can analyze whether the function uses free variables, functional parameters or functional values, and then decide based on the situation - Yes Store data on the stack or in the heap.

Implementation of ECMAScript closures
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).
Copy code The code is as follows:

var x = 10;

function foo() {
alert(x);
}

(function (funArg) {

var x = 20;

// variable "x "Static saved in the (lexical) context, saved when the function is created
funArg(); // 10, instead of 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, I suggest you read Chapter 14 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 whether this function will be activated later - [[Scope]] will be there when the function is created):
Copy code The code is as follows:

var x = 10;

function foo() {
alert(x);
}

// foo is closure
foo: = {
[[Call]]: ,
[[Scope]]: [
global: {
x: 10
}
],
... // Other properties
};

As we said, for optimization purposes, when a function is not used In the case of free variables, the implementation may not be stored in the side effect domain chain. 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 has a non-standard __parent__ attribute for the [[Scope]] attribute of the function, which was introduced in Chapter 12:
Copy code The 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 via the scope chain
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, modifications made by a certain closure to the variables of [[Scope]] will affect the reading of its variables by other closures:

That is to say: all internal functions share the same Parent scope
Copy code The 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"], in [[Scope]] shared by 2 closures

alert(firstClosure()) ; // 3, through the first closure's [[Scope]]
}

foo();

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

There is a very common misunderstanding about this function. Developers often do not get what they expect when creating functions in loop statements (counting internally) result, whereas the expectation is that each function has its own value.
Copy code The 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 code The code is as follows:

activeContext.Scope = [
... / / Other variable objects
{data: [...], k: 3} // Active 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 has become 3. As shown below, creating a closure can solve this problem:
Copy code The 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
}

// Now the result is correct
data[0](); // 0
data[1](); // 1
data[2](); // 2

let Let’s take a look at 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:
Copy code The code is as follows:

data[0].[[Scope]] === [
... // Other variable objects
Active objects in the parent context AO: {data: [. ..], k: 3},
Active object AO in the context of _helper: {x: 0}
];

data[1].[[Scope]] === [
... // Other variable objects
Active object AO in parent context: {data: [...], k: 3},
Active object AO in _helper context: {x : 1}
];

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

We see that the [[Scope]] attribute of the function has the really desired value at this time. In order to achieve 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 by other means, as follows:
Copy the code The 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 also correct
data[0] (); // 0
data[1](); // 1
data[2](); // 2

Funarg and return
Another feature is returned from the 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.

The ECMAScript standard exit behavior is as follows:
Copy code The 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 getElement function
alert('found: ' element); // found: 2
return element;
}

} );

return null;
}

However, the following effect can be achieved through try catch in ECMAScript:
Copy code The 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
$break.data = element;
throw $break;
}

});

} catch (e) {
if (e == $break) {
return $break.data;
}
}

return null;
}

alert(getElement()); // 2

Theoretical version
Let me explain here that developers often mistakenly understand closures as simplified 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 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 versions of the definition of closure in ECMAScript:

In ECMAScript, closure refers 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:
It still exists even if the context in which it was created has been destroyed (for example, the inner function returns from the parent function)
Free variables are referenced in the code
Practical usage of closures
When used in practice, closures can create very elegant designs, allowing customization of various calculation methods defined on funarg. The following is an example of array sorting, which accepts a sorting condition function as a parameter:
Copy code The code is as follows:

[1, 2, 3].sort(function (a, b) {
... // Sorting condition
});

The same example is also Yes, the map method of an array maps the original array to a new array based on the conditions defined in the function:
Copy code The code is as follows:

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

Using functional parameters, you can easily implement a search method and support unlimited search conditions:
Copy code The 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 code The 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 already been introduced when discussing "this"; here, we think of them as application functions - functions that are applied to arguments (the argument list in apply, the individual arguments in call):
Copy code The code is as follows:

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

Closure There is another very important application - delayed call:
Copy code The code is as follows:

var a = 10;
setTimeout(function () {
alert(a); // 10, after one second
}, 1000);

There are also callbacks Function
Copy code The code is as follows:

//...
var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
// It will be called when the data is ready;
// Here, no matter which context it is created in
// At this time the value of variable "x" already exists
alert(x); // 10
};
//...

Also You can create encapsulated scopes to hide helper objects:
Copy the code The code is as follows:

var foo = {};

// Initialization
(function (object) {

var x = 10;

object.getX = function _getX() {
return x;
};

})(foo);

alert(foo.getX()); // Get closure "x" – 10

Summary
This article introduces more theoretical knowledge about ECMAScript-262-3, and I think these basic theories are helpful in understanding the concept of closure in ECMAScript. If you have any questions, I will reply to you in the comments.
Other references
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn