preface

This series is a study note, and I’m going to write down what I’ve learned from JavaScript you don’t know. If you’re interested, join me.

The build process

We know that the JS is a dynamic language for programmers, the language can’t advance at build time to compile added Typescript (can), but for procedure, JS is still a compiled language, only the timing of its compilation and other static type programming language is different, but the way its compilation steps are similar

Traditional compiled language

In the flow of a traditional compiled language, a piece of source code in a program goes through three steps, collectively called “compilation,” before it is executed.

  • Word segmentation/lexical analysis

Is responsible for splitting a string into code blocks. For example, var a=2 will be split into var, a, 2,; , etc.

  • Parsing/parsing

Parsing lexical units after word segmentation, such as the above code is decomposed into abstract syntax trees (AST)

VariableDeclaration (var) / \ Identifier (Identifier) AssignmentExpression (assignment expression) variable a | Numericliteral (numeric literal) number2
Copy the code
  • Code generation

Code generation translates the AST into machine instructions that open up memory, store values in that memory, and so on

JS

JS adds additional performance optimizations, such as garbage handling, during parsing and code generation.

And JS compilation is completed a few microseconds before execution, which means that the code is already executed by compilation time for the programmer.

Process three elements

When working with program code, there are three distinct elements: compiler, engine, and scope

The compiler is responsible for the compilation process described above

The engine is fully involved in compiling the execution environment

The scope is responsible for all declared access rights throughout the process, and it sets a strict set of rules

scenario

So let’s say I have var a =2 and for the engine, these are two different declarations one for compile time and one for run time

The compiler handles:

When var a is encountered, it asks if the scope already has an existing variable. If so, the compiler ignores this declaration and continues compiling. If not, the scope is required to declare a new variable in the collection of the current scope and name it a.

2. The compiler generates runtime code for the engine, which is used to handle the a=2 operation.

Engine runtime processing:

Query scope, if there is a variable in the current scope, assign the value, if not, find the next level of scope continue to find, and assign the value of a=2

Engine lookup

The engine queries the scope and looks for variables. There are two ways to look for RHS and LHS. Simple memory R is right, L is left, and the benchmark is the identifier.

For example, var a=2, a on the left is LHS, and a on the right is RHS

Of course, it’s best not to just memorize =, because many forms of lookups don’t depend on the = symbol, for example

function fn(a){
    console.log(a)
}
fn(2)
Copy the code

So fn(2) is going to look back at fn, and in this case it’s going to get the fn function, so for the fn variable, it’s going to be an RHS lookup. There’s a little detail here, 2 finds the variable a, assigns it a value, so it’s like a=2, so it’s an LHS lookup.

It is helpful to understand the concept of target and source of assignment operations to distinguish LHS from RHS.

Console. log(a) is an RHS query.

Scope nesting

A scope is responsible for setting a set of rules for how identifiers are identified and found throughout the process, which affects the engine’s ability to find variables.

If the current scope engine does not find the variable, it looks in the outer layer until it reaches the global scope.

abnormal

It is necessary to distinguish BETWEEN RHS and LHS because this will affect the prompt after finding an exception, for example

var a=2
console.log(a)
console.log(b)
Copy the code

There will be two RHS queries and one LHS query, where LHS is 2 assigned to A and RHS is log reading the values of A and B.

When the value of B is read, it will prompt ReferenError because no declaration can be found

LHS queries, however, are different. If no declaration is found in the global scope, the global scope will kindly help to create a global variable. Try the following code

function fn(){
	a=2
    console.log(a)
}
fn() / / 2
console.log(a) / / 2
Copy the code

The above code does not declare a. When LHS query is performed in memory, it will look to see if there is a declaration of A. If there is no declaration of a, it will create a global A

var a // Global scope help is created
function fn(){... }Copy the code

Tips: The strict mode introduced after ES5 prevents automatic creation of global variables

When the RHS variable looks for a variable, but you do something you shouldn’t, such as fetching a value from undefined, TypeError will be raised

summary

  • A scope is a set of rules that help the engine find variables. When the engine cannot be found in this scope, it looks in the outer layer.

  • There are two types of rules for engine lookup: RHS and LHS.

  • It is an RHS lookup when you need to get a value inside a variable, and an LHS lookup when you need to assign a value to the variable.

  • If RHS does not find it, ReferenError is reported, and if it does, TypeError is reported

  • If LHS fails, a global scope will be created in non-strict mode, with ReferenError in strict mode

  • The assignment operator causes an LHS query. The = operator or an operation passing in an argument when calling a function results in an assignment of the associative scope

Lexical scope

As mentioned above, the first stage at compile time is word segmentation/lexical analysis. This working stage is also called lexing. The lexing process checks the source code and, if it is a stateful parsing process, gives the word semantics.

This concept is fundamental to understanding lexical scope.

To put it simply, lexical scope is the scope of the definition at the lexical stage, and in a more general way, wherever you write in the scope block is the scope.

For example

var a ="A in global scope"
var b="B in global scope"
function fn(){
	var a = "A at the first level"
    console.log(a) //" a under the first function"
   function fn2(){
    	var a="A under the second layer function"
        console.log(a) //" a under layer 2 function"
        console.log(b)//" global scope b"
        console.log(window.a)//" a in global scope"
    }
    fn2()
}
console.log(a)//" a in global scope"
fn()
Copy the code

There are three different scopes, namely global, FN, and fn2. When looking for the value of b in fn2, the inner scope cannot be found, so the outer scope search will be revealed, and the scope search will stop when the first matching identifier is found.

You can define identifiers with the same name in multiple nested scopes, which is called the “shadowing effect.” The global scope is attached to the window, so global variables Shadowed by the same name can be accessed through window.a, but not inside the function.

Only layer 1 identifiers can be accessed, not read across layers.

var a={value:1}
function fn1(){
  var a={name:2}
  function fn2(){
    console.log(a.value)
}
  fn2()
}
fn1()//undefined
Copy the code

We can only access the value of the first level function a through the above code. We cannot go over the value of the global scope.

Cheating on lexical

The lexical scope is defined entirely by the position declared by the function during code writing, but we can trick the lexical scope in two ways.

Note: The following two methods can cause performance degradation. Generally, do not use them.

eval

Eval is a function that takes a string as an argument and treats its contents as if they existed at that point in the program at the time of writing. In other words, you can programmatically generate code in the code you write and run it as if it were written in that location.

In carrying out the eval (..) In subsequent code, the engine does not “know” or “care” that the previous code was inserted dynamically and modified to the lexical scope of the environment. The engine only does lexical scoping lookup as usual.

function fn(str){
	eval(str);
    console.log(b)
}
var b=2
fn("var b=1")
Copy the code

Var b=1 (eval); var b=1 (eval); var b=1 (eval)

This doesn’t happen in strict mode, but nobody can use it

So try not to use eval

with

With is often used as a shortcut to refer repeatedly to multiple properties in the same object without having to refer repeatedly to the object itself. For example,

var obj={a:1.b:2.c:3}
// repeat references to code
obj.a
obj.b
obj.c
/ / to use with
with(obj){
	a=2
}
obj.a / / 2
Copy the code

The above code is actually missing obj. This method is deprecated, but it is a bug. Run the following code

function foo(obj){
with(obj){
a=2}}var o1={a:3}
var o2={b:3}
foo(o1)
console.log(o1.a) / / 2
foo(o2)
console.log(o2.a) //undefined
console.log(a) / / 2
Copy the code

When we modify the attribute A of O2, we don’t modify it because it doesn’t exist. When we print O2. a, it is undefine, which also confirms this situation, but JS looks up a=2 as LHS, generating a new lexicon, resulting in the generation of global scope A.

In fact, there are some deceptive lexicographs that we try not to use because they can affect the performance of the JS engine.

summary

Lexical scope is where you write your code in the block-level scope.

We can cheat lexical scopes by using eval or with, but this would result in a performance penalty. Using any of these mechanisms will cause your code to run slowly. Don’t use them.

Function scope and block-level scope

Each function has its own scope, and according to the principle of least exposure and least privilege, we should use the characteristics of scope to hide the variables. The advantage of this is to avoid global variable contamination.

There are currently two ways to hide variables

1. Execute functions immediately to wrap global variables, thus preventing contamination of global function names

(function(){} () ()function(){}) ()Copy the code

2, modular processing similar to react, Vue modular, in fact, different files do not affect each other

Block-level scope

There was no block-level scope prior to ES6, which has been said by many, but not necessarily true

In fact, both the with keyword and try-catch create a block-level scope that is affected by {} outside of the function scope. Variables defined inside the scope are not useful to the outer layer

Let changes the situation, we should all use let declaration, let not only create block level scope, even remove var variable promotion declaration, but still have a temporary dead zone problem (if you want to understand this article please read schrodinger variable promotion

The garbage collection

Block-level scope is also a remarkable characteristics, is to optimize the garbage collection mechanism, imagine if we perform a function contains the current scope, inside the closure, because of the closure mechanism make the data, so there are some useless under the current scope of variables will have always existed, but after add {}, is equivalent to tell the engine, This code can be deleted as soon as it is executed.

So we should always use {} to surround declared variables.

Let cycle

Try comparing the two pieces of code

for(let i=0; i<6; i++){console.log(i)
}
console.log(i)
//-----
for(var i=0; i<6; i++{console.log(i)
}
console.log(i)
Copy the code

You’ll notice that I, defined using var, becomes a global variable. Let’s don’t

Note that the LET declaration is attached with a new scope instead of the current scope.

Const has the same effect as let, except that const is a constant declaration that must be assigned and cannot be modified

summary

It should be good programming practice to wrap variables in functional or block-level scopes when using declarations, and to follow the principles of least exposure and least privilege.

Embrace ES6 as much as possible, using let or const declarations to prevent unnecessary confusion and hijacking of variables in var code

ascension

For the var variable lift, see Schrodinger’s variable lift

JavaScript you Don’t Know explains more

The principle of

The reason is that during compilation, part of compilation is finding all declarations and wrapping them in scopes, which also produces lexical scopes

Therefore, for compilation, everything declared should be processed first, followed by code execution, so that the relationship between scope and executing code is clear.

Otherwise, the scope is messed up. This is what the original JavasCript designers had in mind.

Here’s the code

console.log(a)
var a=2
Copy the code

It’s going to be treated like this

var a
console.log(a) //undefined
a=2
Copy the code

A similar situation occurs with named function functions, where I can put the call above the definition.

Note, however, that function expressions are not promoted.

foo() //TypeError
var foo=function bar(){}
Copy the code

The difference between TypeError and ReferenceError is that TypeError indicates that the RHS query was successful but the operation failed, while ReferenceError indicates that the query was unsuccessful.

Var foo has been promoted, but it’s not yet a function, it’s undefined, and you can’t call undefined.

What if I change the code to something like this?

foo()
bar()
var foo=function bar(){}
Copy the code

What happens when I use a function expression and a named function in the code above?

//TypeError
//ReferenceError
Copy the code

Foo was found but the operation failed. Bar never found the object. It can be understood in the following form.

var foo
foo()
bar()
foo=function(){
	var bar=...
}
Copy the code

Function is preferred

Functions are first-class citizens of JavaScript; for declarations, declaring the same variable at the same time takes the function.

foo() / / 1
var foo
function foo(){console.log(1)}
foo =2
Copy the code

In the above code, function foo overwrites the var declaration promotion, resulting in the following code

// var is overwritten
function foo(){console.log(1)}
foo()
foo =2
Copy the code

We could also say that var is the first thing that goes up, and then it gets overridden by functions, whatever. In short, JS chooses to promote the function.

summary

For us, var a =2 might be just a statement, but for the JS engine, it’s divided into compile and execute tasks.

Var a is used to generate code, and the scope has read and write permissions.

When the engine runs, it looks for variable A based on scope and finally gives an assignment.

This gives rise to a mechanism whereby variable declarations in the same scope are prioritized regardless of where they are placed, a process known as promotion.

Declarations are promoted, but assignment operations (including function expressions) are not.

Should we avoid duplicate declarations at all times to avoid unnecessary bugs

Scope and closure

One thing to remember: closures occur when we somehow get the scope inside a function

function foo(){
	var a=2
    function bar(){
    	console.log(a)
    }
    return bar
}
var baz= foo()
baz() / / 2
Copy the code

The lexical scope of bar() has access to the scope inside foo(), and by passing bar we achieve the closure effect.

The effect is not just to access the function’s internal scope; it also stops the garbage collector from reclaiming foo() ‘s memory space.

In the code above, the variable A and function bar have closures that cover the scope of foo. Make this scope always exist. We can call the variable a+bar function a closure.

No matter how an inner function is passed outside its lexical scope, it holds a reference to the original definition scope and uses closures wherever the function is executed.

6个6

Look at this code

for(var i=0; i<6; i++){setTimeout(() = >{console.log(i)},1000)}Copy the code

What does it print out?

We expected to print one to five, one per second, but what happened?

It prints six 6’s

Why is that?

Because setTimeout does not execute until the end of the loop. At this point I is already 6.

Why is that?

Because we imagine that each iteration in the loop captures itself a copy at runtime, but in the code above, all share the same scope, and within that scope, there is only one I.

It’s like this

var i
for (i=0; i<6; i++){ ... }Copy the code

There’s only one I, only one scope.

We need to create more scopes at this point.

Changing var to let creates multiple scopes.

You can also use the immediate execution function

for(var i=1; i<=5; i++){ (function(){setTimeout(() = >{console.log(i)},i*1000()})})Copy the code

The above code does not work because the immediate function is scoped, but there is always only one I and we need to create more I inside it.

for(var i=1; i<=5; i++){ (function(i){				setTimeout(() = >{console.log(i)},i*1000)})(i)
}
Copy the code

The code above passes in I each time, which is equivalent to generating multiple I’s in multiple scopes. The problem is solved.

ES6 modular

ES6 import allows you to import one or more apis from a module into the current scope and bind them to a variable

The module imports and binds the entire module API to a variable. Export exports an identifier (variable, function) for the current module as a public API. These operations can be used as many times as needed in the module definition. The contents of a module file are treated as if they were contained in a scope closure, just like the function closure modules described earlier.

conclusion

Here ARE a few questions to conclude the article and if you can answer them, you have basically mastered the chapter

1. What is lexical scope?

2. Why is there variable promotion?

3. Features of function promotion

What is a closure?

5. Closure characteristics

Reference documentation

Weread.qq.com/web/reader/…