Details about JavaScript scopes and closures
JavaScript is a widely used language. It also has some characteristics and advantages. This article focuses on its scope mechanism and closure, and will discuss its mechanism from some examples. The scope has different meanings in the daily use of JavaScript programmers, as shown below: The value bound by this; the execution context defined by the value bound by this; the "Life Cycle" of a variable "; variable value parsing scheme, or lexical binding. The following describes the concept of JavaScript scope, which leads to the general idea of the variable value parsing scheme. Finally, we will discuss the important knowledge point of closure in JavaScript. 1. Global scope all browsers support window objects, which indicate browser windows. JavaScript global objects, functions, and variables are automatically members of window objects. Therefore, the global variable is the property of the window object, the global function is the method of the window object, and even the document of the html dom is one of the properties of the window object. A global variable is the longest variable in JavaScript (How long does a variable keep a certain value). It will span the entire program and can be accessed by any function method in the program. Variables declared in the global environment are all in the global scope under the window object. We can access the variables through the window object or directly. 1 var name = "jeri"; 2 console. log (window. name); // output: jeri3 console. log (name); // output: jeri is at any position in JS. variables that are not declared using the var keyword are also global variables.
1 function fun () {2 name = "jeri"; 3 alert (name); 4} 5 6 console. log (name); // output: jeri
Global variables exist throughout the life cycle of the function. However, they are easily tampered with globally. When using global variables, be careful not to use them. Declaring variables in a function without using var will also generate global variables, which will cause some confusion, such as variable overwrite. Therefore, it is best to include var whenever a variable is declared. Global variables exist throughout the life cycle of the program, but we can certainly access global variables without referencing them. 2. Lexical scopes lexical scopes: Functions run in the scopes that define them, rather than in the scopes that execute them. That is to say, the lexical scope depends on the source code and can be determined through static analysis. Therefore, the lexical scope is also called static scope. Except with and eval, we can only say that the JS scope mechanism is very close to the Lexical scope ). The Lexical scope can also be understood as the visibility of a variable and the simulated value of its text representation.
1 var name = "global"; 2 3 function fun () {4 var name = "jeri"; 5 return name; 6} 7 8 console. log (fun (); // output: jeri9 console. log (name); // output: global
In general, the query of a variable starts from the binding context closest to external expansion until the first binding is found. Once the search is complete, the search is completed. In the preceding example, the name = "jeri" closest to it is searched. After the query is complete, the first obtained value is used as the value of the variable. 3. In programming practices, dynamic scopes are the most likely to be underestimated and overly abused, because few languages support this method to bind resolution solutions. The dynamic scope is relative to the lexical scope. Unlike the definition of the lexical scope, the dynamic scope is determined during execution, and its lifecycle ends when the code snippet is executed. Dynamic variables exist in the dynamic scope. Any bound value is unknown before the function is called. During code execution, the corresponding scope chain is often static. However, when a catch in the with statement, call method, apply method, and try-catch statement is encountered, the scope chain is changed. Take the with statement as an example. When a with statement is encountered, the passed object attribute is displayed as a local variable to facilitate access. That is to say, a new object is added to the top of the scope chain, this will inevitably affect the parsing of local flags. After the with statement is executed, the scope chain is restored to the original state. Example:
1 var name = "global"; 2 3 // use 4 console before. log (name); // output: global 5 6 with ({name: "jeri"}) {7 console. log (name); // output: jeri 8} 9 10 // after using with, the scope chain resumes 11 console. log (name); // output: global
When there is a dynamic scope in the scope chain, this reference will become more complex, instead of pointing to the context at the first creation, but determined by the caller. For example, when the apply or call method is used, the first parameter that is passed in is the referenced object. Example:
1 function globalThis () {2 console. log (this); 3} 4 5 globalThis (); // output: Window {document: document, external: Object ...} 6 globalThis. call ({name: "jeri"}); // output: Object {name: "jeri"} 7 globalThis. apply ({name: "jeri"}, []); // output: Object {name: "jeri "}
Because this reference is a dynamic scope, you must pay attention to the changes in this reference during programming and track the changes in time. 4. Function scope function scope, as its name implies, is the scope generated when the function is defined. This scope can also be called a local scope. Unlike global scopes, function scopes are generally accessible only in code snippets of functions, and variables cannot be accessed externally. Variables defined in a function exist in the function scope, and the lifecycle ends with the execution of the function. Example:
1 var name = "global"; 2 3 function fun () {4 var name = "jeri"; 5 console. log (name); // output: jeri 6 7 with ({name: "with"}) {8 console. log (name); // output: with 9} 10 console. log (name); // output: jeri11} 12 13 fun (); 14 15 // cannot access function scope 16 console. log (name); // output: global
5. no block-level scope is different from other programming languages, and there is no block-level scope in JavaScript, that is to say, the declared variables in the for, if, while statements are the same as those in the external declaration. The values of these variables can also be accessed and modified outside these statements. Example:
1 function fun () {2 3 if (0 <2) {4 var name = "jeri"; 5} 6 console. log (name); // output: jeri 7 name = "change"; 8 console. log (name); // output: change 9} 10 11 fun ();
6. Everything in JavaScript of the scope chain is an object, including a function. Function objects, like other objects, have attributes that can be accessed by code and a series of internal attributes that are only accessible by the JavaScript engine. One internal attribute is the scope, which contains the set of objects in the scope created by the function. It is called the scope chain of the function, it is used to ensure orderly access to the variables and functions that the execution environment has the right to access. After a function is created, its scope chain will be filled with accessible data objects in the scope of the function. When a function is created in the global scope, its scope chain automatically becomes a member of the global scope. When a function is executed, its activity object becomes the first object in the scope chain (activity object: The object contains all the local variables, naming parameters, parameter sets, and this of the function ). During program execution, the Javascript engine parses identifiers such as variables and function names by searching the context scope chain. The search starts from the innermost part Of the scope chain in the order from the inner to the outer until the search is completed. Once the search is completed, the search ends. If no identifier declaration is found, an error is returned. When the function execution ends, the runtime context is destroyed, and the activity object is also destroyed. Example:
1 var name = "global", 2 age = "0"; 3 4 function fun () {5 // if no activity object is found, query 6 consoles along the scope chain. log (name); // output: global 7 name = "change"; 8 // The global variable 9 console can be modified in the function. log (name); // output: change10 // first query the activity object. If yes, stop querying 11 var age = "18"; 12 console. log (age); // output: 1813} 14 15 15 fun (); 16 17 // After the function is executed, the execution environment is destroyed. If no variable declaration is found, the error 18 console is reported. log (age); // output: Uncaught ReferenceError: age is not defined
7. Closure is a major mystery of JavaScript. There are many articles on this issue. However, a considerable number of programmers still do not fully understand this concept. Closures are officially defined as an expression (usually a function) that has many variables and an environment bound to these variables. Therefore, these variables are part of this expression. In a word, closure is a function that captures external bindings within the scope. These bindings are bound for later use, even if the scope has been destroyed. The relationship between the free variable and the closure is that the free variable is closed in the creation of the closure. The logic behind the closure is that if a function contains other functions, these internal functions can access the variables declared in this external function (these variables are called free variables ). However, these variables can be captured by internal functions. The return statement of a higher-order function (the function that returns another function is called a higher-order function) is "jailbroken" for future use. Variables used by internal functions before any local Declaration (neither passed in nor local Declaration) are captured variables. Example:
1 function makeAdder (captured) {2 return function (free) {3 var ret = free + captured; 4 console. log (ret); 5} 6} 7 8 var add10 = makeAdder (10); 9 10 add10 (2); // output: 12
From the above example, the variable captured in the external function is captured by the return function that executes the addition. The internal function has never declared the captured variable, but can reference it. If we create another generator, the variable captured with the same name will be captured, but there are different values, because this one is created after makeAdder is called: 1 var add16 = makeAdder (16 ); 2 3 add16 (18); // output: 244 5 add10 (10); // output: 20 as shown in the above Code, each new multiplier function retains the captured instance captured during creation. Variables are masked in JavaScript. When a variable is declared within a certain scope and then declared in another variable with the same name in a lower scope, the variable is masked. Example:
1 var name = "jeri"; 2 var name = "tom"; 3 4 function glbShadow () {5 var name = "fun"; 6 7 console. log (name); // output: fun 8} 9 10 glbShadow (); 11 12 console. log (name); // output: tom
When a variable is declared multiple times in the same scope, the last declaration takes effect, and the previous declaration is masked. The masking of variable declarations is easy to understand, but the masking of function parameters is slightly complicated. For example:
1 var shadowed = 0; 2 3 function argShadow(shadowed) { 4 var str = ["Value is",shadowed].join(" "); 5 console.log(str); 6 } 7 8 argShadow(108); // output:Value is 108 9 10 argShadow(); // output:Value is
The parameter shadowed of the argShadow function overwrites the variable with the same name in the global scope. Even if no parameters are passed, shadowed is bound and the global variable shadowed is not accessed. In any case, the nearest variable has the highest binding priority. Example:
1 var shadowed = 0; 2 3 function varShadow(shadowed) { 4 var shadowed = 123; 5 var str = ["Value is",shadowed].join(" "); 6 console.log(str); 7 } 8 9 varShadow(108); // output:Value is 12310 11 varShadow(); // output:Value is 123
VarShadow (108) does not print 108, but 123. Even if no parameter is input, it prints 123. Access the closest variable to bind. The masking variable also occurs inside the closure. The example is as follows:
1 function captureShadow(shadowed) { 2 3 console.log(shadowed); // output:108 4 5 return function(shadowed) { 6 7 console.log(shadowed); // output:2 8 var ret = shadowed + 1; 9 console.log(ret); // output:310 }11 }12 13 var closureShadow = captureShadow(108);14 15 closureShadow(2);
When writing JavaScript code, variable masking will cause many variable bindings to exceed our control. We should avoid variable masking as much as possible and pay attention to variable naming. The following are typical mistakes that have plagued many people and are also discussed below.
1 var test = function () {2 var ret = []; 3 4 for (var I = 0; I <5; I ++) {5 ret [I] = function () {6 return I; 7} 8} 9 10 return ret; 11}; 12 var test0 = test () [0] (); 13 console. log (test0); // output: 514 15 var test1 = test () [1] (); 16 console. log (test1); // output: 5
From the above example, we can see that after the test function is executed, an array of functions is returned. On the surface, each function in the array should return its own index value, but this is not the case. After the external function is executed, although the execution environment of the external function has been destroyed, the closure still retains the reference to the variable binding, and remains in the memory. After the external function is executed, the internal function is executed. The variable bound to the internal function is the final variable value after the external function is executed, therefore, all these functions reference the same variable I = 5. Here is a more elegant example to illustrate this problem:
1 for (var I = 0; I <5; I ++) {2 3 setTimeout (function () {4 console. log (I); 5}, 1000); 6} 7 8 // output a 5
According to our inference, the above example should output 1, 2, 3, 4, 5. However, in fact, the output is 5 consecutive 5. Why is this strange situation? Essentially, it is caused by the closure feature. The closure can capture variable binding in the external scope. During the execution of the above function segment, the internal and external functions are not executed in sync, because when setTimeout is called, a delay event will be thrown into the queue. After all the synchronized code is executed, then, the latency events in the queue are executed in sequence, and at this time I is already 5. How can this problem be solved? Can we send a copy of the variable to the internal function during every loop execution so that it captures a variable binding every time it creates a closure. Because each time we pass different parameters, the variable binding captured each time is also different, thus avoiding the final output of five. Example:
1 for (var I = 0; I <5; I ++) {2 3 (function (j) {4 5 setTimeout (function () {6 console. log (j); 7}, 1000); 8}) (I); 9} 10 11 // output: 1, 2, 3, 4, 5
The closure has very powerful functions. The function can reference external parameters and variables, but its parameters and variables will not be returned by the garbage collection mechanism. The resident memory will increase the memory usage, improper use can easily cause memory leakage. However, closures are also a major feature of javascript. The main application of closures is mainly to design private methods and variables. Simulation of private variables from the above description we know that the capture of variables occurs when the closure is created, then we can take the variables captured by the closure as private variables. Example:
1 var closureDemo = (function () {2 var PRIVATE = 0; 3 4 return {5 inc: function (n) {6 return PRIVATE + = n; 7}, 8 dec: function (n) {9 return PRIVATE-= n; 10} 11}; 12}) (); 13 14 var testInc = closureDemo. inc (10); 15 // console. log (testInc); 16 // output: 1017 18 var testDec = closureDemo. dec (7); 19 // console. log (testDec); 20 // The output is 321 22 closureDemo. div = function (n) {23 return PRIVATE/n; 24}; 25 26 var testDiv = closureDemo. div (3); 27 console. log (testDiv); 28 // output: Uncaught ReferenceError: PRIVATE is not defined
After the execution of the function closureDemo is complete, the scope and PRIVATE of the Self-executed function are destroyed, but the PRIVATE is still stuck in the memory, that is, added to closureDemo. inc and closureDemo. in the scope chain of dec, the closure completes variable capture. However, the newly added closureDemo. div cannot be found in the scope. Because the function only executes the code in the function when it is called, and the variable is captured only when the closure is created, the newly added div method cannot capture PRIVATE. You can create private scopes by using the closure to create privileged methods, so that you can create private variables and private functions. The method for creating a private function is the same as that for declaring a private variable. You only need to declare the function within the function. Of course, since we can simulate private variables and private functions, we can also use the closure feature to create privileged methods. Example:
1 (function () {2 3 // Private variable and Private function 4 var privateVar = 10; 5 6 function privateFun () {7 return false; 8 }; 9 10 // constructor 11 MyObj = function () {12 13}; 14 15 // public/privileged method 16 MyObj. prototype. publicMethod = function () {17 privateVar ++; 18 return privateFun (); 19} 20 })();
The above instance creates a private scope and encapsulates a constructor and corresponding methods. Note that in the above example, when declaring the MyObj function, the function expression without var is used. We want to generate a global function instead of a local function, otherwise, we will still be unable to access the service. Therefore, MyObj becomes a global variable that can be accessed externally. The publicMethod defined on the prototype can also be used, in this way, we can access private functions and private variables. In general, some powerful functions can be implemented through closure due to its peculiar characteristics. However, in our daily programming, we should also use the closure correctly. We should always pay attention to recycling unused variables to avoid Memory leakage. This article introduces the concept of closure step by step through the introduction of the scope system, discusses in detail the mechanism of closure generation and some common errors, and finally applies the closure as an example. However, my personal skills are limited, but I hope you will forgive me for any omissions. Welcome!