--Excerpt from "You Don T Know js-scope, Closures"
For all programming languages, scopes are a fundamental concept. An in-depth understanding of the scope in JavaScript plays an important role in the correct use of the language.
What is a scope
Scopes are rules for how a set of variables are stored and read, and there are two types of models:
- Static scopes (also known as literal scopes, lexical scopes).
- Dynamic scopes.
Operation of the scope
There are two types of operations on scopes: read operations, write operations.
The number of operands read in the compilation principle is called the right operand (RHS), and the operand modified is called the operand (LHS).
This name comes from an assignment expression: a = B;
A in the left is lhs,b on the right for RHS.
Static scope
Variables in JavaScript are scoped to a static scope.
Static scopes are determined when the code is written, and can be nested.
As shown below, there are three scopes in the code snippet, and there is a strict nesting relationship between scopes:
Scope 3 is a subset of scope 2, and scope 2 is a subset of scope 1.
function Foo (a) { var b = A * 2; function Bar (c) {Console.log (A, B, c);} bar (b * 3);} Foo ( 2); // 2 4
Finding a variable
The JavaScript execution engine starts the query operand from the current scope (LHS,RHS) and, if the operand is not in the current scope, performs a lookup up to the global scope.
If the global scope still does not find the operand, the query fails.
So the output of the example code is 2 4 12.
Behavior of operand query failure
RHS query failed, throw Referenceerror exception.
LHS query fails, a variable with the same name is created in the global scope. (in strict mode, the Referenceerror exception is thrown).
Hack scope
The Hack scope is not a recommended behavior, but it can help in-depth understanding of the scope of JavaScript. You can modify the scope of the execution code with the eval or with keyword.
Eval modifies a scope that already exists
The code in eval runs in the current scope and modifies or accesses the number of operands that can be queried in the current scope.
As shown below:
function foo (str, a) { // cheating! var b = 2//1, 3
"var B = 3;" Runs in the scope of function foo () and declares variable B in this scope.
The variable B in the function foo () scope overrides B in the global scope, so the program output is 1, 3.
In strict mode, the code in eval runs in the new scope, which is a subset of the current scope.
As shown below:
function foo (str) { "use strict"; eval (str); // referenceerror:a is not defined "var a = 2");
"var a = 2;" Run in a separate scope, this scope is a subset of the function foo () scope, so Console.log (a) throws a Referenceerror exception when querying right operand a.
With Create a new scope
The WITH keyword creates a separate scope for the object, which is a subset of the current scope.
function foo (obj) { with (obj) { = 2; var o1 = { 3var o2 = { 3/// // undefined// 2--Oops, leaked global!
Foo (O1) looks for operand A in the O1 scope and modifies its value, so the Console.log (o1.a) output is 2.
Foo (O2) found in the O2 scope to do operand A, the query failed. Continuing to find in the parent scope function foo () also fails. The global scope also does not have the definition of exercises left number A.
Following the left operand query rule, the JavaScript execution engine creates a new variable A in the global scope and assigns a value of 2, and a scope leak occurs.
function scope
Before ES3, a function is the only way to create a scope. Each function creates a scope and is nested within the current scope.
The operands defined in the function scope can be used anywhere in this function or in its child scope, but not in the ancestor scope.
The function scope can therefore be used as a namespace to prevent multiple module naming collisions.
As shown below:
function DoSomething (a) { function dosomethingelse (a) { return A-1; var= a + Dosomethingelse (A * 2 ); * 3//
function Dosomethingelse () is accessible only in the functions dosomething () scope, which plays the role of encapsulation.
ES3 before the function is the smallest unit of scope. The operands that are created anywhere in the function are scoped to this function.
As shown below:
function foo () { function Bar (a) { // changing the ' I ' in the enclosing scope ' s fo R-loop Console.log (A + i); for (var i=0; i<10; i++) { // oops, infinite loop ahead! }}
This code will fall into a dead loop. Although "var i = 0" is declared in a for loop, because the function is the smallest unit of scope, I is scoped to function foo ().
function bar () modifies I to 3, leading to the occurrence of a dead loop.
function expression
A function creates a scope that encapsulates its interior and prevents naming conflicts. However, the name of the function is the main source of the naming conflict.
The function expression resolves the problem of a function name conflict.
How to distinguish function definitions from function expressions
The entire statement begins with the function keyword, which is defined by the functions.
Conversely, the function is identified as an expression of functions.
As shown below:
function // function Definition (function// functions Expression
Scope of function names in function expressions
The function expression creates a new scope, which is a subset of the current scope.
With (function foo () {...}) For example, the function name Foo in this function expression can be accessed only in the location shown in "...".
Block scope
ES3 specifies that the try/catch in the Catch statement defines the variable, scoped to this catch statement.
As shown below:
Try { // Illegal Operation to force an exception! }catch (err) { // works! // referenceerror: ' Err ' not founde
The scope of err is only a catch statement, so Console.log (err) throws a Referenceerror exception.
Let keyword
The Let keyword was introduced in ES6. Let and var are used to define variables.
The difference is:
- var defines the scope of a variable as a function, and let defines a variable scoped to a code block.
- The location where the VAR definition occurs does not affect its references in the scope, and the variables defined by let are only referenced after they are defined.
As shown below:
var true ; if (foo) { // <--explicit blocklet bar = foo * 2; = something (bar); Console.log (bar); // Referenceerror
{ // referenceerror! Let bar = 2;}
Let loop
Let to define a variable in a for loop, this variable is scoped to the for loop.
As shown below:
for (Let i=0; i<10; i++) { // referenceerror
Let's bind the scope of I to a for loop, and each time the loop is bound, it can be illustrated with the following code:
{let j; for (j=0; j<10; j + +) { // re-bound for each iteration! Console.log (i); }}
Const
The variable that is defined by the Const keyword introduced in ES6 is scoped to a code block.
Unlike the Let keyword, a const is defined as a constant and cannot be modified.
As shown below:
var true ; if (foo) { var a = 2; // block-scoped to the containing ' if ' // just fine! // error! // 3// referenceerror!
Closed Package
What is a closure package
A closure is the ability of a function to access its ancestor scope when it is executed outside its static scope.
As shown below:
function foo () { var a = 2; function Bar () { console.log (a); } return Bar;}
var baz =// 2--whoa, closure was just observed, man.
The Foo () function returns its intrinsic function bar ().
The function bar () is called outside its static scope (Baz ()) and still has access to variable a in its ancestor scope.
This capacity is called closures.
It can be said that the closure of Bar () contains the scope of Foo (). The original is: Function bar () has a closure over the scope of Foo ()).
Closures and loops
When closures and loops are tangled together, the situation becomes interesting.
Now implement a code output of 1 2 3 4 5, each time the output interval is 1 seconds.
Take a look at the following code:
for (var i=1; i<=5; i++) { function timer () { console.log (i); }, I* );}
The function timer () accesses the variable I,timer () in its ancestor scope, which is called by the timer, and the call is in its static domain, thus constituting the closure.
The output of the code is five 6.
The reason is that the timer runs at the earliest in 1 seconds, when the loop is executed and the value of I becomes 6. Because I is scoped to the function where the for loop is located, the timer () five executions get the latest value of I.
Does calling the function expression immediately (iife) solve this problem?
The function expression creates a new scope, but does it solve the problem?
Take a look at the following code:
for (var i=1; i<=5; i++) { (function() { function timer () { Console.log (i); }, I*1000 ); }) ();}
After execution, the result is still five 6.
The reason is that although this function expression creates a new scope, the scope is empty, and the final timer () function refers to I in a higher-level scope.
What if I store the current value of I inside the function expression scope?
Take a look at the following code:
for (var i=1; i<=5; i++) { (function() { var j = i; function timer () { console.log (j); }, J*1000 ); }) ();}
Results after execution are 1 2 3 4 5
Because the function expression creates a new scope, five loops produce 5 copies of the scope, and J stores the different values for I in each copy.
the timer () function executes in different copies of the scope and prints out different values.
The above code is a more concise notation:
for (var i=1; i<=5; i++) { (function(j) { function timer () { Console.log (j); }, J*1000 ); }) (i);}
Can the Let keyword solve this problem?
Let's scope is block, can solve this problem?
Take a look at the following code:
for (var i=1; i<=5; i++) { // yay, block-scope for closure! function timer () { console.log (j); }, J*1000 );}
The execution result is 1 2 3 4 5.
Because the domain is a block, the values of J in the five copies of the five cycles are different, so the output values are different.
When you define a loop variable with let, the scope of the variable is a for loop, and each loop is bound once to the scope (producing a copy), so the above code can be further refined to:
for (Let I=1; i<=5; i++) { function timer () { console.log (i); }, I* );}
javascript--scopes and closures