preface

These days, I happened to discuss with my friends about closures, the “god beast” in JavaScript. Many students will think that closures are too annoying to understand. I was like that when I first started JavaScript.

But closures are important! Very important! Very important! The JavaScript You Don’t Know even goes so far as to say that “for those who have had a bit of JavaScript experience but never really understood the concept of closures, understanding closures can be seen as something of a rebirth”.

So are you eager to take a closer look at closures? There is a very important prerequisite to understanding closures: scope and lexical scope. If you don’t understand lexical scope well, then closures will never be understood! So let’s take a closer look at lexical scope.

scope

We first throw out the concept that “lexical scope is a working model of scope”. Regardless of the underlying meaning of this statement, we should conclude that there is no lexical scope without the concept of scope. So… Next thing you know…

What is scope

In short, “a scope is a set of rules for determining where and how to look for variables (identifiers).” Finding variables (identifiers) is a key point to read in this sentence, so let’s start with finding variables.

Let’s start with a very simple piece of code

function foo() {
	var a = 'iceman';
	console.log(a); / / output "iceman"
}
foo();
Copy the code

Var a = ‘iceman’; var a = ‘iceman’;

Let’s look at another piece of code that’s just as simple

var b = 'programmer';
function foo() {
	console.log(b); / / output "programmer"
}
foo();
Copy the code

In the same way, if the variable b is not found inside the function, it will be searched in the outer global. If it is found, it will stop searching and output.

Note that both pieces of code have lookup variables, the first finding variable A in the function, and the second finding variable B globally. Now close your eyes, I’m going to add a few words to those words in bold!

Now, open your eyes, Duang, Duang –> function scope, global scope, put those two words into the original sentence, the first code is to find a variable in function scope, the second code is to find b variable in global scope.

So, is that clear? In layman’s terms, a scope is a place to look up variables. If you find the variable in a function, you can say that the variable is found in the function scope. If you find the variable globally, you can say you found it in the global scope!

I don’t know if you have noticed one detail. When we look for variable B, we first look in the function scope. If we don’t find it, we look in the global scope. We seem to be looking for variables up a chain, which we call the scope chain.

Scope nesting

Before we touch let and const of ES6, there is only function scope and global scope. Function scope must be inside the global scope, and function scope can continue to nest function scope, as shown in the figure below:

In code:

The above two figures can be very intuitive to see the scope of the nesting relationship. The search variable also follows the red arrow, from inside to outside, which makes up the scope chain.

Lookup rules for variables (identifiers) in scope

First of all, JavaScript is compiled, and not surprisingly, it is! Var name = ‘iceman’ var name = ‘iceman’

  • The compiler declares a variable name in the current scope

  • The runtime engine looks for the variable in scope, finds the name variable and assigns it a value

Prove the above statement:

console.log(name); / / output is undefined
var name = 'iceman'; 
Copy the code

Var name = ‘iceman’; var name = ‘iceman’;

The compiler works by compiling from top to bottom before executing the code. When it encounters a variable declared with var, it checks to see if the variable exists in the current scope. If so, ignore the declaration; If not, the variable is declared in the current scope.

The simple code above contains two lookup types: RHS for printing the value of a variable and LHS for finding the value assigned to it.

I’m sure you can guess what “L” and “R” mean, where “left” and “right” refer to the left and right side of an assignment. That is, LHS queries are performed when variables appear on the left side of an assignment and RHS queries are performed when variables appear on the right.

In layman’s terms, RHS takes its source value.

Note: “left and right of assignment” does not mean just “=”, there are actually several forms of assignment.

ReferenceError: a variable is not defined in the global scope. ReferenceError: a variable is not defined in the global scope. ReferenceError: a variable is not defined

The lookup variable in all assignment operations is LHS. So assignments like a=4 will also look in the current scope, and if they don’t find it, they’ll look in the outer scope, and if they go to the global variable, the global variable, in non-strict mode will create a global variable. However, this is not recommended because at best it can pollute global variables or at worst it can leak memory (for example, a = a very large array, a is always referenced in global variables, and the program will not destroy it automatically).

Lexical scope

In the scope introduction above, we defined a scope as a set of rules that govern how the browser engine performs variable lookups based on variables (identifiers) both in the current scope and in nested scopes.

We threw out the concept earlier that “lexical scope is a working model of scope”. There are two working models of scope, lexical scope being the dominant one in JavaScript and dynamic scope (used by fewer languages).

The so-called lexical scope is determined by where you write the variable and block scopes when you write the code, that is, the lexical scope is static, determined when you write the code.

Take a look at the following code:

function fn1(x) {
	var y = x + 4;
	function fn2(z) {
		console.log(x, y, z);
	}
	fn2(y * 5);
}
fn1(6); / / 6 10 to 50
Copy the code

This example has three nested scopes, as shown below:

  • A is the global scope with one identifier: fn1

  • B is the scope created by fn1 and has three identifiers: x, y, and fn2

  • C is the scope created by fn2 and has an identifier: z

Scope is determined by where the code is written and is included level by level.

It is emphasized here that lexical scope is defined by the position of the function declaration when the code is written. Compile time knows where all identifiers are and how they are declared, so lexical scope is static, that is, lexical scope can predict how identifiers will be found during code execution.

Note 1: Eval () and with can be used to “cheat” lexical scopes by virtue of their specificity, but they are not normally recommended because of performance issues.

Note 2: In ES6, let and const have block-level scope, which will be covered later.

Pay special attention to

You can follow my official account: icemanFE, and continue to update technical articles!