author
If you want to reprint, please indicate the source. If you feel good, don’t be stingy with your star
1. What is scope
Domain, in Chinese, means a range. So the scope is literally the scope of the scope, which is close to the actual scope. But it’s still not quite right. A scope is essentially a set of rules for determining where and how to look for variables (identifiers).
To understand the rules of where and how to look, we need to understand a few concepts.
First, JavaScript(later simply JS) is often thought of as a ‘dynamic ‘,’ interpreted execution’ language. Dynamic typing, static typing, weak and weak typing are involved.
Js is actually a compiled language. It’s just not pre-compiled. In most cases, the code is compiled before execution.
For compilation, there are three steps:
- Lexical analysis
- Syntax analysis
- Code generation
In a nutshell,
Lexical analysis
Var a = 2; At the lexical stage it is split into var, a, =, 2,; Whitespace is ignored at this stage.
Copy the code
Syntax analysis
Code generation
So we can conclude that var a = 2; What does the engine and compiler behind this code do when it executes?
There are only two actions to execute this code: first check the scope to see if a variable with the same name exists, and then create a new one. The engine then finds the A in scope and executes the generated code to assign it a value.
How do I find this A here? There are two concepts, LHS and RHS
LHS & RHS
The engine looks for two rules for variable A in scope. This can be easily divided by the assignment operator ‘=’.
Variables are queried LHS to the left of the = sign. The purpose of the query is to assign values. Variables are RHS queries to the right of the = sign. The purpose of a query is to get a value.
This is to better understand the meaning of LHS and RHS, but the ‘=’ division is too absolute. A better understanding is that LHS is to find the target of the assignment. RHS is the source of assignment operations. In other words, RHS is equivalent to getting the value of a variable, LHS is to find the variable itself and assign it a value.
A case to analyze:
function foo(a){
console.log(a)
}
foo(2)
Copy the code
This simple code snippet Outlines the search process:
- RHS query foo in foo(2), the last sentence, because we want to get its value.
- The function foo (a) {… }, and assign 2 to it.
- Perform an RHS query on console and verify that it has a log() function.
- Perform an RHS query on a in log(a) to obtain its value of 2.
With that in mind, we have a general idea of the lookup rules in scope. What about the scope of the search? That’s actually where to look. A scope not only defines a set of rules for finding, but also defines a scope for itself. This range can still be nested.
The scope nesting rule is simply:
If the current scope is not found, the outer nested scope is searched until it stops, or the outer layer (global scope) is not found until it stops.
If a global search is not found until it is found, the search will stop.
In fact, this is why there are two types of lookup rules, LHS and RHS purposes, their query behavior is not the same.
Such as:
function foo(a){
console.log(a, b);
b = a;
}
foo(2);
Copy the code
The RHS query for b is performed in console.log(b) here. Obviously B has not been declared at this point.
ReferenceError is thrown if the RHS query does not find a variable in the entire scope hierarchy.
PS: Notice the difference between ReferenceError and TypeError.
But LHS lookups in non-strict mode have different results. The LHS lookup, if not found at the top level, creates a variable with the same name. And back to the engine.
In “Use Strict” mode, LHS will raise a ReferenceError if it is not found globally
ReferenceError & TypeError
ReferenceError represents a declaration that the variable was not found in scope; TypeError means that the variable is declared globally, but an incorrect, illegal operation has been performed on its result
Lexical scope is the scope of the definition at the lexical stage. To put it bluntly, where your code is written and what structure defines the lexical scope. Lexical scope is therefore the most common and ubiquitous form of scope in our development.
Look at the following code:
function foo(a){
var b = a * 2;
function bar(c){
console.log(a, b, c)
}
bar(b * 3)
}
foo(2) // 2, 4, 12
Copy the code
This code defines three nested scopes, the outermost global scope, the scope defined by foo(){}, and the scope defined by bar(){}.
First we specify the rules for nested scope lookups:
Step by step, from the inside out, until the whole world, or find it.
So the above code performs console to look for variables A, b, and C in the scope of bar(), where C is found in the current scope. Then a and B are not in the current scope, so we go one level out and keep looking. Fortunately, we find them in the scope of outer foo(). The RHS lookup is over, and the engine gives us 2,4,12.
Here are a few things to note:
- Masking effect.
function outer(){
var a = 2;
function inner(){
var a = 'Pgone';
console.log(a)
}
inner();
}
outer() // Pgone
Copy the code
The scope search ends when the first matching tag is found, so if there is a variable with the same name in the inner or outer scope, the shadowing effect will occur, and the inner variable with the same name will be used directly.
- Properties in the global scope can be accessed through window.
Use this feature to access shaded global properties using Windows. But non-global attributes are not accessible.
- The lexical scope only looks for first-level identifiers
In the demo above, if we call outer. Inner, the scoped lookup only tries to find the first-level identity foo, and maybe the object attribute access rule takes over the lookup.
Can lexical scopes be changed dynamically?
To do this, we can look at two common magic tricks in our code;
eval & with
Eval is a common function that takes a string and executes it at a location you specify. This modifies the original lexical scope environment.
function foo(str, a){
eval(str);
console.log(a, b)
}
var b = 2;
foo('var b = 3', 1) // 1, 3
Copy the code
The string ‘var b = 3; ‘was executed in scope inside foo(), and the engine missed it. It still does the flow pull down, causing the declared b to overshadow the global b, thus printing 1,3.
Note that in strict mode, eval() has its own independent scope, and the above method fails.
A string can be inserted dynamically and executed in many ways:
- SetTimeout () The first argument to setInterval() can be a string, interpreted as dynamically generated function code.
- The last argument to new Function() can also take a code string
2. with
The most common use of with() is to facilitate access to an object’s properties
var obj = {
a:1,
b:2
}
with(obj){
a=3;
b=4;
}
Copy the code
The interesting thing about with is that it creates a completely separate scope for this object. This can be a problem;
function foo(obj){ with(obj){ a = 2; } } var o1 = { a : 1; } var o2 = { b : 1; } foo(o1); console.log(o1.a) //a foo(o2); Console. log(O2.a)//undefined console.log(a) //2 --> Leaked globally
Copy the code
The above code works in non-strict mode, where with is largely disabled.
Inside foo(), with creates a separate scope for obj. The LHS query is performed on A. So if foo(o1) is executed, we can find o1.a in outer foo(o1) and assign to it. If foo(o2) is executed, we can find o2.a and assign to it. This is a RefferenceError. If you can’t find it, it should be RefferenceError. We print the global a and find that it is indeed a=2. There is no way to create one in non-strict mode with this 2 LHS query.
Best practice: Eval and with should both disappear from our code.
Function scope, as its name implies, is the lexical scope defined by the structure of the function definition. All variables inside the function can be used and reused throughout the scope of the function.
The most immediate benefit of function scope for development is that it perfectly matches the principle of minimum exposure. That is, we can use a function scope to cover variables or functions that we don’t want to expose to the outside world. So the outside world can’t access it directly.
It can also avoid global pollution and naming conflict by using function action on. We are familiar with some frameworks, such as backbone. js,Jquery are the outermost structure of IIFE.
For example, an implementation where we write a CO module is the common UMD pattern:
'use strict' ; (function(window,definition,undefined){ var hasDefine = typeof define === 'function' && (define.cmd || define.amd), hasExports = typeof module ! == 'undefined' && module.exports; if(hasDefine){ define(definition); }else if(hasExports){ module.exports = definition(); }else{ window.co = definition(); } })(window,function(){ function co(gen){ var args = [].slice.call(arguments,1), it; it = gen.apply(this,args); return Promise.resolve().then(function handleNext(value){ var next = it.next(value); return (function handleResult(next){ if(next.done){ return next.value; }else{ return Promise.resolve(next.value).then( handleNext, function handleError(err){ return Promise.resolve( it.throw(err) ).then(handleResult); }); } })(next) }) } return co; })
Copy the code
Block scope
Block scopes should be familiar to you if you have experience developing in other languages. Although the most common type of scope in JavaScript is function scope, there are still other types of scope. Block scope is one of them.
What are the benefits of block scope? Let’s take a look at a code we wrote badly:
for(var i = 0, len = 10; i < len; i++){
console.log( i );
}
Copy the code
What we really want is for I to bind to {… } inside the use, the outside can not operate. But the fact is that the I declared by var is still bound to the outer scope.
function foo( a ){ function bar(a){ i = 2; // The LHS query here finds I in the for loop. So... Ha ha... return a + i; } for(var i = 0, len = 10; i < len; i++){ bar( i * 2 ) } }
Copy the code
The result above is an endless loop. Because I is not bound to {… } inside, but in the outer scope.
1. The with mentioned earlier creates a separate block scope.
try… catch… Catch (){catch(){… } statement blocks are a typical block scope. The outside world is inaccessible.
try{ 10 / 0; Log (err)} console.log(err) // ReferenceError: Err not found
Copy the code
With this feature, we can implement block scope in pre-ES6 environments. Although it will be ugly.
We know that in ES6, the following code is ok:
{
let a = 2;
console.log( a ); //2
}
console.log( a ); // ReferenceError
Copy the code
If you want to implement in a pre-ES6 environment:
try{throw 2; }catch(a){console.log( a )} // 2 console.log( a ) //ReferenceError
Copy the code
Block scope in ES6
New in ES6, let allows you to declare a variable, statement, or expression whose scope is limited to the block level. Unlike the var keyword, the variable declared by var can only be global or the entire function block.
// let
var a = 3;
{
let a = 2;
console.log(a) // 2
}
console.log(a) // 3
Copy the code
Var declares variables to be promoted, function declarations to be promoted (function expressions do not), and let?
Var a = 3; { //let a; console.log(a) // ReferenceError: a is not defined let a = 2; //a = 2; // undefined }
Copy the code
The let is bound to the block-level scope and is not initialized. If the variable is called before it is declared and initialized, a ReferenceError is thrown. Because at this point the variable is still in the temporary dead zone (TDZ). Undefined is thrown if called after the declaration and before the initialization.
Let declarations should be wrapped around {… }, should be written at the top of the block level scope, after all, there is no promotion, in case something goes wrong. It is recommended that you use only one let if you have multiple declarations.
{
let a = 2, b, c;
}
Copy the code
It should also be noted that global variables declared by lets are not properties of global objects, and you cannot pass the window. To access. Our common Typeof is no longer secure. For example, typeof operations on undeclared variables will raise an error, but typeof operations on undeclared variables will not raise an error, just undefined
If (typeof a === "undefined") {console.log("cool"); // 'a' is not declared if (typeof a === "undefined") {console.log("cool"); // if (typeof b === "undefined") {// ReferenceError! / /.. } / /.. let b; }
Copy the code
There is also a nonstandard way of writing that was not adopted in ES6, although the meaning is clearer:
let(a = 2, b, c){
//...
}
Copy the code
To reinforce your understanding of let, consider the following code:
// consider for let demo1
let a = 2;
if(a > 1){
let b = a * 3;
console.log(b); // 6
for(let i = a ; i <= b ; i++){
let j = i + 10;
console.log(j) // 12,13,14,15,16
}
let c = a + b;
console.log(c) // 8
}
Copy the code
Q1: Which variables only exist in the if and which variables only exist in the for loop?
A1: block-level scoped variables B, c exist only in if; Only present in the for loop are the block-level scoped variables I, j.
This simple demo gives you an idea of the scope of the block scope, where I is in for, because, wherever the let declaration is, it is attached to the enclosing function scope and bound to the block level scope.
let + for
The let/for loop is a perfect match. Some of the problems with the var+for loop can be solved perfectly. For example:
// for loop for(var i = 0 ; i <= 5 ; i++){ setTimeout(function(){ console.log(i); // Output 6}, I *1000)} console.log(I); // Finally output 6
Copy the code
Replace with let, and then:
for(let i = 0 ; i <= 5 ; i++){ setTimeout(function(){ console.log(i); 0,1,2,4,5}, I *1000)} console.log(I); // ReferenceError: i is not defined
Copy the code
The comparison of the two demos illustrates a number of let features:
- The let is bound to the block-level scope of the for loop and is not promoted, so the outside world cannot access the I.
- Var has only one public, promoted declaration, so overrides occur. The output is the last value.
- In each iteration, let declares a new I for the iterator as well as an I for the for. Each iteration is a new I, so the closure in the for loop is also closed with the value you expect, as shown in demo3:
// demo3 // for loop with let and closure var arrfunc = []; for(let i = 0 ; i < 5 ; i++){ arrfunc.push(function(){ console.log(i) }) } arrfunc[3](); // 3 If it is var, output 5
Copy the code
Dig deeper: in layman’s terms for loops, () and {} correspond to different scopes
Another special aspect of the for loop is that the statement part of the loop is a parent scope, while the body of the loop is a separate child scope
for(var i = 0 ; /* scope a*/ I < 3; console.log('in for expression', i), i++){ let i; // the scope of a is different from that of a. Console. log('in for block', I)} in for block undefined in for expression 0 in for block undefined in for expression 1 in for block undefined in for expression 2
Copy the code
for(…) Var I, let I, {… } you can get the value of I directly. If var is declared, it is easier to understand. } can get the external I value, also because there is only one common I, so it will overwrite, but let? How do you send values? My personal understanding is probably as follows (code for Demo3):
{ let i = 0; { let _i = i; arrfunc.push(function(){ console.log(_i); }}})
Copy the code
In other words, there’s actually a new I iterating every time. This illustrates the let’s nature of binding new values in the iterations mentioned in the above article. And it’s bound to the previous value.
Variables declared by const are similar to those declared by let, except that they can only be explicitly assigned at declaration time and cannot be modified at will, resulting in a SyntaxError.
It is also worth noting that a const declaration does not mean that the value pointed to cannot be changed, but that it can only be explicitly assigned once.
// const const MAX_NUM = 100; MAX_NUM = 1000; // TypeError: Assignment to constant variable. const MIN_NUM // SyntaxError: Unexpected identifier MIN_NUM = 100; Const ARR_NUM = [1,2,3] ARR_NUM. Push (4); console.log(ARR_NUM); // 1,2,3,4 const ANOTHER_NUM = 7; if(true){ //var ANOTHER_NUM = 10; //console.log(ANOTHER_NUM); //SyntaxError: Identifier 'ANOTHER_NUM' has already been declared let ANOTHER_NUM = 100; console.log(ANOTHER_NUM); // 100 } console.log(ANOTHER_NUM); / / 7
Copy the code
Constants have block scope, much like variables defined using let. The value of a constant cannot be changed by reassignment, nor can it be redeclared. A constant cannot have the same name as any other variable or function in its scope
I’ve read articles that say that using const declarations improves performance, but I’m not sure. Use const wisely and clearly. This is best used if you want to tell someone that the variable can no longer be assigned. Don’t rely too much.
Block scoped function
Starting with ES6, function declarations can be defined in block scope. Prior to ES6, the specification did not require this, but many implementations did. Norms now meet reality.
{
foo(); // it works
function foo(){
console.log('it works')
}
}
foo(); //ReferenceError: foo is not defined
Copy the code
The above example illustrates:
- Functions can be declared in a block scope and cannot be accessed by the outside world.
- Function declarations can be promoted in block scope. Attention should be paid.
So pay attention to some of our writing habits:
if(true){
function foo(){
console.log('1')
}
}else{
function foo(){
console.log('2')
}
}
foo(); //in ES6: ReferenceError: foo is not defined
//in Pre-ES6: 2
Copy the code
To make this simple, let’s say you have a large piece of data that is passed to a method as an argument. If the function has a closure that covers the entire scope, the garbage collector will collect it. That’s what the hell it is.
The block scope eliminates the concern of the collection mechanism, and the collection is unscrupulous.