Closures are an important feature in JavaScript, and their biggest function is to save information during function operation. In JavaScript, many features of closures are derived from the scope chain during function calls.
Scope chain of function call objects and variables
For each function call in JavaScript, JavaScript will create a local object to store the local variables defined in the function; if there is a nested function inside the function, JavaScript will define a nested local object on the already defined local object. For a function, there are as many layers of nested function definitions as there are, as many layers of nested local objects as there are. This local object is called "function call object" ("call object" in ECMAScript 3, and was renamed "declarative environment record" in ECMAScript 5, but I personally think that the name in ECMAScript 3 is easier to understand). The following function calls are used as an example:
The code copy is as follows:
function f(x){
var a = 10;
return a*x;
}
console.log(f(6));//60
In this simple example, when the f() function is called, JavaScript will create a call object of the f() function (let's call it f_invokeObj). There are two properties inside the f_invokeObj object: a and x; when f() is run, the a value is 10 and the x value is 6, so the final return result is 60. The illustration is as follows:
When there is function nesting, JavaScript will create multiple function call objects:
The code copy is as follows:
function f(x){
var a = 10;
return a*g(x);
function g(b){
return b*b;
}
}
console.log(f(6));//360
In this example, when calling the f() function, JavaScript will create a call object of the f() function (f_invokeObj), which has two attributes a and x, and the value a is 10 and the value x is 6; when running f(), JavaScript will parse and define the g() function in the f() function and create a call object of g() (g_invokeObj), which has an attribute b, and the value b is the same as the passed parameter x, so the final return result is 360. The illustration is as follows:
As you can see, the function call object forms a chain. When the embedded function g() is running and needs to obtain the variable value, it will start searching from the most recent function call object. If it cannot be searched, searching in a further call object along the function call object chain, which is the so-called "scope chain of variables". If the same variable appears in the two function call objects, the function will take the variable value in the call object closest to it:
The code copy is as follows:
function f(x){
var a = 10;
return a*g(x);
function g(b){
var a = 1;
return b*b*a;
}
}
console.log(f(6));//360, not 3600
In the above example, the variable a has different values in the calling object (g_invokeObj) of the g() function and the calling object (f_invokeObj) of the f() function. When running the g() function, the value of a used inside the g() function is 1, while the value of a used outside the g() function is 1. The function call object chain at this time is as follows:
What is a closure?
All functions in JavaScript are objects, and when defining functions, a corresponding chain of function calls objects will be generated. A function definition corresponds to a chain of function calls objects. As long as the function object exists, the corresponding function call object exists; once a function is no longer used, the corresponding function call object will be garbage collected; and this combination of function object and the chain of function call object is called "closure". In the above examples of f() function and g() function, there are two closures: the f() function object and the f_invokeObj object form a closure, and the g() function object and the g_invokeObj-f_invokeObj object chain form the second closure. When the g() function is executed, since the g() function is no longer used, the g() closure is garbage collected; then, when the f() function is executed, the f() closure is also garbage collected for the same reason.
From the definition of closure, we can draw a conclusion: all JavaScript functions are closures after definition because all functions are objects, and all functions also have their corresponding call object chains after execution.
However, what makes closures really work is the case of nested functions. Since the embedded function is defined only when the external function is running, the variable value stored in the closure of the embedded function (especially the local variable value of the external function) is the value during this run. As long as the embedded function object still exists, its closure still exists (the variable value in the closure will not change any), thus achieving the purpose of saving the information of the function running process. Consider the following example:
The code copy is as follows:
var a = "outside";
function f(){
var a = "inside";
function g(){return a;}
return g;
}
var result = f();
console.log(result());//inside
In this example, when the f() function is run, the g() function is defined, and a closure of the g() function is created. The g() closure contains the g_invokeObj-f_invokeObj object chain, so the value of variable a during the f() function execution is saved. When the console.log() statement is executed, the g() closure still exists because the g function object still exists; when running this still-existing g function object, JavaScript will use the still-existing g() closure and get the value of variable a ("inside") from it.