A brief analysis of JavaScript scope chain execution context and closure

  • 2020-12-09 00:39:56
  • OfStack

Closure and scope chain are important concepts in JavaScript. In the past two days, I reviewed some materials and summarized the related knowledge points for everyone as follows.

JavaScript is lexically scoped (lexical scoping), and the variables on which function execution depends are scoped at the time the function is defined, not at the time the function is executed. The following code snippet illustrates, in general (based on the realization of the stack, such as C language) foo scope after the called function's local variables will be released, but look from the lexical foo embedded in the anonymous function scope should mean foo scope local variables, and actually code running results with the expression of 1 on lexical, are called f returned after local scope. The function object f retains a reference to the body scope variable of the foo function after its body function foo is called, which is called a closure.


var scope = 'global scope';
function foo() {
var scope = 'local scope';
return function () {
return scope;
}
}
var f = foo();
f(); //  return  "local scope"

So how exactly do closures work? Understanding a closure first requires understanding the variable scope and scope chain, and another important concept is the execution context.

Variable scope

In JavaScript, global variables have a global scope, and variables declared in the body of a function have a global scope, which is local, as well as nested functions defined in the body of a function. The priority of local variables is higher than that of global variables. If local variables have the same name with global variables, global variables will be covered by local variables. Local variables defined within the same nested function take precedence over the local variables of the function in which the nested function resides. This is so obvious that almost everyone knows it.
Now let's talk about something that might be new to you.

Function declaration promotion

Function declaration promotion is defined in 1 sentence, meaning that variables declared within the body of the function are valid throughout the function. In other words, variables declared at the bottom of the function body are also promoted to the top. Here's an example:


var scope = 'global scope';
function foo() {
console.log(scope); //  It's not going to print out here  "global scope" , but  "undefined"
var scope = 'local scope'; 
console.log(scope); //  Obviously, print it out  "local scope"
}
foo();

The first console. log(scope) prints undefined instead of global scope because the declaration of the local variable has been raised, just not yet assigned.

Variables as attributes

In JavaScript, there are three ways to define global variables, globalVal1, globalVal2, and globalValue3 in the sample code below. One interesting fact is that global variables are really just properties of the global object window/global (window in the browser, global in node.js). In order to conform to the common definition of variables, JavaScript designed global variables defined in var to be unerasable global object properties. Object.getOwnPropertyDescriptor (this, 'globalVal1') can be obtained, and its configurable attribute is false.


var globalVal1 = 1; //  Global variables that cannot be deleted 
globalVal2 = 2; //  Global variables that can be deleted 
this.globalValue3 = 3; //  with  globalValue2
delete globalVal1; // => false  Variables are not deleted 
delete globalVal2; // => true  Variables are deleted 
delete this.globalValue3; //=> true  Variables are deleted 

So the question is, are local variables defined in the body of a function also properties of an object? The answer is yes. This object is related to the function call, called "call object" in ECMAScript 3 and "declaravite environment record" in ECMAScript 5. This particular object is an invisible internal implementation for us.

The scope chain

As we learned in the previous section, a function local variable can be viewed as an attribute of an invisible object. The implementation of JavaScript's lexical scope can be described as follows: each piece of JavaScript code (global or function) has a scope chain associated with it, which can be an array or a linked list structure. Each element in the scope chain defines a set of scoped variables; When we are looking for the value of the variable x, we look for the variable in the first element of the scope chain, and if no finder looks for the next element in the list until we find or reach the end of the chain. Understanding the concept of scope chains is critical to understanding closures.

Execution context

The execution of each piece of JavaScript code is tied to the execution context in which the running code gets information about variables, functions, data, and so on that are available. The global execution context is unique and bound to the global code, with each execution of a function creating an execution context to which it is bound. JavaScript maintains the execution context through the stack's data structure. The global execution context is at the bottom of the stack. When 1 function is executed, the newly created function execution context is pushed onto the stack, the execution context pointer points to the top of the stack, and the running code gets the execution context of the currently executed function binding. If the body of the function executes a nested function, an execution context is also created and pushed on the stack. The pointer points to the top of the stack. When the nested function finishes running, the execution context bound to it is pushed off the stack, and the pointer points back to the execution context of the function binding. Also, when the function finishes executing, the pointer points to the global execution context.

An execution context can be described as a data structure containing a variable object (corresponding global)/active object (corresponding function), scope chain, and this. When a function executes, the active object is created and bound to the execution context. Active objects include variables declared in the function body, functions, arguments, and so on. Scope chains, as mentioned in the previous section, are built according to lexical scope. It is important to note that this is not an active object and is determined at the moment the function is executed.
Execution contexts are created in a specific order and phase, and each phase has a different state. See Resources 1 for details, which are listed at the end.

closure

Knowing the scope chain and execution context, and going back to the code at the beginning of the article, you can basically explain how closures work. The execution context and lexical scope chain created at the time of the function call holds the information needed for the function call before the f function call returns local scope.

It should be noted that multiple functions defined within a function use the same scope chain, and it is easy to cause errors when using for loop to assign anonymous function objects. Examples are as follows:


var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = {
func: function() {
return i;
}
};
}
arr[0].func(); //  return  10 Rather than  0

arr[0].func() returns 10 instead of 0, deviating from sensory semantics. Before ECMAScript 6 was introduced into let, the variable scope was within the entire body of the function rather than within the code block, so all the func functions defined in the above example referred to the same scope chain after the for loop, and i had become 10.

The right approach is this:


var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = {
func: getFunc(i)
};
}
function getFunc(i) {
return function() {
return i;
}
}
arr[0].func(); //  return  0

This has been helpful in providing an introduction to THE JavaScript scope chain, execution context, and closures.


Related articles: