From how the browser compiles the JS code.
I've been thinking for a long time, when we give the code to the browser, how the browser transforms the code into a vivid Web page. JS engine before executing our code, what the browser did to our code, the process for me is like a black box, mysterious and curious.
Understand
var a = 2
We write var a = 2
a simple JS code like this every day, but the browser is the machine, it can only know the binary 0 and 1, it is var a = 2
certainly more difficult than the foreign language for us. But it doesn't matter, at least we have a clear question now about how it translates meaningful human characters into 0 and 1 of machines that match certain rules.
Think about how we read a sentence (can think of a foreign language we are not so familiar with), when we are not familiar with English, we are in fact a priority to understand is a word, these words in accordance with certain rules become a meaningful sentence. The browser is actually the same var a = 2
, the browser actually see is var
,, a
=
, 2
this is a word. This process is called the lexical parsing phase , in other words, this process breaks down a string of characters into chunks of code that are meaningful (for programming languages).
Just as we combine words as sentences in grammatical rules, the browser also combines the broken-down code blocks into a tree (AST) that represents the syntax structure of the program, which is called the parsing phase , and AST is already a meaningful foreign language for the browser. But it's a short distance away from it. code generation , conversion code for meaningful machine language (binary language).
Let's summarize the three stages of our experience.
- 词法分析:分解代码为有意义的词语;* 语法分析:把有意义的词语按照语法规则组合成代表程序语法结构的树(AST);* 代码生成:将 AST 转换为可执行代码
Through the above three stages, the browser can already run the executable code we get, these three stages there is a call to the compilation phase . We refer to the execution of the executable code later as the run phase .
When the scope of JS is determined
In programming languages, scopes generally have two types, lexical scope and dynamic scope. Lexical scopes are scoped to the code structure that is written when you rely on programming, and generally, after the compilation is complete, the scope is determined and the code is not changed during the run. Dynamic scopes are known to be dynamically changed during code operation. The scope of our JavaScript is generally assumed to be lexical scope (generally speaking, because JavaScript provides some way to dynamically alter the scope, which is described later in this article).
Lexical scopes are the scopes that are determined by the code structure that is written when you rely on programming, and compared to what the browser does at compile time, we find that lexical scopes are determined during the compilation phase . See if this is a sudden understanding of why we used to hear the phrase "the scope of a function is defined in the function definition phase". Next, let's explain what rules the function scope is determined by.
What is the scope scope in JS?
What is a scope? "You don t know JS" gives such a concept:
Use a strict set of rules to tell which identifiers have access to those grammars.
Well, what's an abstract sentence, and what is an identifier ? How does the scope really understand? Let's take a look.
Identifier:
We know that when our program runs, Our data ("string", "Object", "function", and so on are all loaded into memory). So how do we access the corresponding area of memory, where the identifier works, and through it we can find the corresponding data, from this point of view, variable names, function names and so on are identifiers.
Operations on identifiers
Knowing the identifier, let's think about what we do with identifiers in peacetime. In fact, there are two kinds of, look at the following code:
// The first defines the identifier ' a ' and assigns the value 2 to ' a ' which has a special term called ' LHS ' . var a = 2; // The second Kind, var B = A, actually corresponds to a, b two operators are different operations, for B is an assignment operation, this is LHS, but for a is taken to a corresponding value, this operation also has a special term called "RHS" var b = A;
To summarize, there are two types of actions for identifiers:
- 赋值操作(LHS);常见的是函数定义,函数传参,变量赋值等等* 取值操作(RHS);常见包括函数调用
And look back at the scope
Understand the identifier and the two operations of the identifier, we can easily understand the scope, the scope is actually defined our rendering in the runtime, the scope of the identifier operation, corresponding to the actual problem, we are familiar with the function or variable can be called somewhere.
Scopes can also be seen as a set of rules for finding variables by name. So let's take a closer look at this rule, when a variable cannot be found in the current scope, the engine will continue to look in the outer nested scope until the variable is found, or the outermost scope (that is, the global scope) is reached.
The word nesting is mentioned here, and we look at the factors in JS that can form a scope.
Scope type function scope in JS
function scope is the most common scope of JS, the function scope gives us the most intuitive experience is that the intrinsic function can call the variables in the external function. A layer of functions, very intuitive to form a nested scope. But I'm sorry to say that. The title of this article, remember what we often hear "if we assign a value to an undefined variable inside a function, the variable is transformed into a global variable". For me, this sentence was almost memorized, and I never understood it. We understand this sentence from the point of view of the operation of the identifier.
var a = 1; function foo () {// B appears for the first time in function foo b = A;} Foo (); // Global access to B // 1
When we call foo()
, B is actually carried out LHS operation (get the value of a and assigned to B), B front does not exist Var let, etc., so the browser first in the foo()
scope to find the B identifier, the result is not found in B, according to the scope of the rules, the browser will continue to foo()
the outer scope of the lookup identifier B, the result is still not found, indicating that in the scope of this query identifier B There is no already defined B, in the non-strict mode LHS operation will be found in the outermost (that is, the global) defines a B, So b becomes a global variable (strict mode LHS cannot find a return referenceerror error). That would make it understandable. It is also worth noting that the RHS operation of the operator will be different, whether strict or non-strict mode RHS can not find a return referenceerror error (the RHS found the value of unreasonable operation will return an error TypeError
(scope discriminant Success, the operation is illegal.) ))。
Closures: Closures are the natural result of writing code based on lexical scopes, and you don't even have to consciously create closures in order to exploit them. The creation and use of closures is ubiquitous in your code. What you lack is the thinking environment that identifies, embraces, and influences closures according to your own wishes.
Block scope
In addition to the function scope, JS also provides block scopes. We should make it clear that scopes are for identifiers, and that block scopes restrict identifiers {}
.
As provided by ES6 let
, the const
identifier of the method declaration is fixed in the block. try/catch
A statement that is often ignored catch
will also create a block scope.
Ways to change the scope of a function
In general, lexical scopes have been identified during the code compilation phase, and this certainty is actually beneficial, and the code is able to predict how to find them during execution during execution. Can improve the execution efficiency of the code running phase. However, JS also provides a way to dynamically change the scope. eval()
functions and with
keywords.
eval()
Method:
This method takes a string as a parameter and treats the contents as if it were a code that existed at this point in the program at the time of writing. In other words, you can generate code and run it in the code you write, as if the code were written in that location.
function foo (str,a) {eval (str); // spoofing scope, the lexical phase stage Foo () function does not define an identifier, but temporarily defines a B in the function run phase; Console.log (A, b);} var b = 2; Foo ( "var b = 3;", 1); // 1,3 // In strict mode, ' eval () ' produces its own scope and cannot be modified by its scope function foo (str) { ' use strict ' // REFERENCEERROR:A is not defined ' var a =2 ');
eval()
Sometimes useful, but the performance is very expensive, it may also bring security risks, so it is not recommended.
with
Key words:
With is often used as a shortcut to repeatedly reference multiple properties in the same object.
varobj ={A:1, B:2, C:3 }; //Tedious repetition of "obj" obj.a = 2;obj.b= 3; OBJ.C= 4; //a simple shortcut with(obj) {a= 3; b= 4; C= 5; } functionfoo (obj) { with(obj) {a= 2; } } varO1 ={A:3 }; varO2 ={b:3 }; Foo (O1); Console.log (o1.a); //2foo (O2); Console.log (o2.a); //undefinedConsole.log (a);//2--is not good, a was leaked to the global scope! //a LHS query was executed, and a global creation was not present. //The with declaration actually creates a completely new lexical scope based on the objects you pass to it.
with
can also lead to loss of performance.
The JavaScript engine performs several performance optimizations during the compilation phase. Some of these optimizations rely on the ability to perform static analysis based on the morphology of the code, and to predetermine the definition of all variables and functions in order to quickly locate identifiers during execution.
Declaring elevation
Scope is related to the scope of the identifier, and the scope of the identifier and its declaration position are closely related. js
There are some keywords that are specifically used to declare identifiers (for example,, var
let
const
), and the definition of non-anonymous functions also declares identifiers.
About the statement perhaps everyone has heard of the word ascension. Let's analyze the cause of the claim elevation.
We already know that the engine compiles the JavaScript code first before it is interpreted. Part of the work in the compilation phase is to find all the declarations and associate them with the appropriate scopes (the core of the lexical scope).
In that case, the statement seems to have been mentioned earlier.
It is important to note that each scope will be promoted. The declaration is promoted to the top of the scope where it is located.
However, not all declarations are promoted, and the weights of different claims are different, and in particular, function declarations are promoted, and function expressions are not promoted (even in the case of a named function expression).
The var
variables that are defined are promoted, let
and const
the declarations that are made are not promoted.
Both function declarations and variable declarations are promoted. But a notable detail is that the function is promoted first, then the variable, that is, if a variable declaration and a function declaration have the same name, the identifier will point to the related function even if the variable is declared before the statement order.
If a variable or function has a repeating declaration, it is first declared as primary.
The last thing to note is:
The declaration itself is promoted, and the assignment operation, including the assignment of a function expression, is not promoted.
Some applications of scopes
See here, I think everyone on the scope of JS should have a more detailed understanding. Let's talk about some of the extended applications of the JS scope.
Principle of least privilege
Also called minimum authorization or minimum exposure principle. This principle means that in software design, the necessary content should be exposed to a minimum, while other content is "hidden", such as the API design of a module or object. is to privatize as many parts of the code as possible.
Functions can produce their own scopes, so we can implement this principle in a way that functions encapsulate (both function expressions and function declarations can be used).
// function Expression var a = 2;(function// <--Add this line var a = 3; // ////2
By the way, here's how to differentiate between function expressions and function declarations :
If the function is the first word in the declaration, then it is a functional declaration, otherwise it is a function expression.
The most important difference between a function declaration and a function expression is where their name identifiers will be bound. A function expression can be anonymous, and a function declaration cannot omit the name of a functor--which is illegal in JavaScript syntax.
It can be encapsulated using a function expression (Iife) that executes immediately.
function expression immediately executed (Iife)
var a = 2; (function foo () { var a = 3; // 3 })(); // 2
The function expression is executed immediately after a parenthesis is appended.
(function(){ .. }())
It's another way of expressing iife. Parentheses are added to the inside and outside, and the function is the same.
By the way, another very common advanced usage of iife is to use them as function calls and pass parameters in.
var a = 2; (function iife (global) { var a = 3; // 3 Console.log (GLOBAL.A);//2 }) (window); // 2
Closed Package
This is what everyone would describe as closures.
When the return value of a function is another function, and the returned function calls the other variables inside its parent function, if the returned function is executed externally, a closure is generated.
function foo () { var a = 2; function Bar () { console.log (a); } return bar; } var baz = foo (); // 2--This is the effect of closures. An identifier//bar () function that accesses a function outside of a function holds a reference to its parent scope, leaving the parent scope without being destroyed, which is the closure
In general, due to the existence of a garbage collection mechanism, the function is destroyed after execution and no longer uses the memory space. In the example above foo()
, it is natural to consider recycling because the content that appears is no longer being used. The "magic" of closures is what prevents this from happening (there has been a lot of talk about reducing the use of closures and the fear of memory leaks, but this is not much more worrying).
In fact, the above definition, long before I knew, but at the same time I also mistakenly thought I usually rarely use closures, because I really did not take the initiative to use the closure, but in fact I was wrong, inadvertently, I have been using closures.
Essentially, whenever and wherever the function (accessing their respective lexical scopes) is treated as a first-level value type and passed around, you will see the closure applied to these functions. In timers, event listeners, Ajax requests, cross-window communications, WEB workers, or any other asynchronous (or synchronous) task, the use of a callback function is essentially a closure!
So you should know that you've used many closures.
Here is a pit that everyone may have encountered, one that did not correctly understand the scope and the closure caused by the closures.
for (var i = 1; I <= 5; i++) { setTimeout (function timer () { console.log (i); * +); } // in fact, the result we want is 1,2,3,4,5, the result is five 6
Let's analyze the cause of the result:
We tried to assume that each iteration in the loop would "capture" a copy of I at run time. However, depending on how the scope works, the reality is that although the five functions in the loop are defined separately in each iteration (previously, the first definition is the primary and the latter is ignored), they are enclosed in a shared global scope because, when the timer function is executed, This I in the global is 6, so I can't reach my expectations.
Understand the scope of the problem, here we have two ways to solve:
//Method 1 for(vari = 1; I <= 5; i++) { (function(j) {SetTimeout (functiontimer () {console.log (j); }, J* 1000); }) (i); //Create a separate scope for Each loop with an immediate execution function. } //Method 2 for(vari = 1; I <= 5; i++) {Let J= i;//Yes, the block scope of closures!SetTimeout (functiontimer () {console.log (j); }, J* 1000); } //Let Each loop create a block scope
Today's development is inseparable from modularity, the following is how the module uses closures.
How the module uses closures:
The most common method of implementing module patterns is often referred to as module exposure .
Let's take a look at how to define a module
functionCoolmodule () {varsomething = "cool"; varanother = [1, 2, 3]; functiondosomething () {console.log (something); } functionDoanother () {Console.log (Another.join (" ! ")); } //returns an object that may contain various functions return{dosomething:dosomething, doanother:doanother}; } varFoo =coolmodule ();//A closure is formed when a method in the return object is called outsideFoo.dosomething ();//CoolFoo.doanother ();//1! 2! 3
Two prerequisites for the module:
Must have an external enclosing function, which must be called at least once
The enclosing function must return at least one intrinsic function in order for the intrinsic function to form a closure in the private scope and to access or modify the private state.
Original link
Scope and closure of JavaScript