preface

Block-level scopes were not supported by JavaScript prior to ES6, so how did JavaScript get itself to support block-level scopes? This article explains the underlying implementation of block-level scopes.

Scope and execution context

Many people think that scope and execution context are the same, which is completely wrong!

scope

A scope is defined when a function is declared. A scope is a set of rules for looking up variables by name, which determines how much access the currently executing code has to a variable. JavaScript supports three types of scope: global scope, function scope, and block-level scope.

Execution context

The execution context is the preparation work done by the JS engine from the interpretation to the execution of the middle of the precompilation, creating the execution environment of the current region, this execution environment is the execution context.

Execution stack

The call stack is used to hold various execution contexts in JS code and is a mechanism for the JS engine to track the execution of functions. Take the following code for example:

console.log(1);
function pFn() {
    console.log(2);
    (function cFn() {
        console.log(3); } ());console.log(4);
}
pFn();
console.log(5);
// Output: 1 2 3 4 5
Copy the code

First, there is the execution context of the global environment. After calling the pFn, the execution context of the function environment pFn is pushed onto the stack. Since the cFn function is executed in the pFn, the execution context of the cFn function is continued to be pushed onto the stack.

The global context is destroyed only before the application exits, such as closing the web page or exiting the browser

How does javascript support block-level scope

We know in js as a result of the initial design, using the var keyword to define variables will lead to ascension and a series of problems, but in order to maintain compatibility, we also have to retain support variables var statement this way, then: JavaScript is how to do is to support variable to ascend, and to support the block-level scope?

Let’s take this code as an example:

function foo() {
	var a = 1;
	let b = 2;
	{
		let b = 3;
		var c = 4;
		let d = 5;
		console.log(a);
		console.log(b);
	}
	console.log(b);
	console.log(c);
	console.log(d);
}	
Copy the code

First, variables declared by var inside the function are stored in the variable environment. Variables declared by let are stored in the lexical environment during the precompilation phase. Of course, variables declared by let in the function body block scope are not stored in the lexical environment.

Continue to execute the code. When it is executed inside the code block, the value of A in the variable environment has been set to 1, and the value of B in the lexical environment has been set to 2. Notice that the variables B and D declared in let are not underpay, but uninitialized

Finally, when the function’s body block scope completes execution, its internal variables are popped from the top of the lexical environment’s stack

conclusion

We can see the answer to the above question: variables declared in let are stored in lexical environment, block-level scope is implemented through the stack structure of lexical environment, and variable promotion is implemented through variable environment. The combination of the two supports both variable promotion and block-level scope

And how variables are found: start at the top of the lexical context’s scope stack, return the value if found, and continue in the variable context if not found

Sharing is not easy, if you feel good, give a free thumbs-up