After reading “front-end interview JavaScript Basics (3) — scope”, we understand the concept of scope, then what is lexical scope?

What is?

Lexical scope is actually one of the working models of scopes that JavaScript follows. It is the scope defined at the lexical stage. We know that the abstract syntax tree (AST) is formed at the lexical stage during compilation, and the corresponding scope is generated at the same time according to the corresponding analysis. It is not difficult to know from this working mechanism which scope an identifier belongs to, and the nested relationships of scopes (scope chains) are already determined at writing time.

Let’s look at a simple example to get a feel for lexical scope:

var a = 'global'

function foo() {
  console.log(a);
}

function bar() {
  var a = 'bar'
  foo()
}

bar()  // global
Copy the code

This is a simple example where some people might think the printed result is bar, but it is not. It is important to note that JavaScript applies the lexical scope model, so the scope of a function depends on where it is declared and does not depend on where it is actually called. The nested scope of the above example is shown in the following figure:

As shown in the diagram above, we know that a is needed when foo is executed, but if we can’t find it in the current scope, the engine will follow the scope chain to the outer layer, and the variable named A happens to be in the global scope, so we print global.

Spoofing lexical scope

Because JavaScript uses the lexical scope model, the scope is defined at writing time, so is there a way to dynamically change the scope at run time?

Of course, let’s look at eval and with and see how they dynamically change scope.

eval

eval(…) The function takes a string as an argument and executes it as if it were code at the time it was written. This spoofs the lexical scope as follows:

var a = 'global'

function foo(str) {
  eval(str)
  console.log(a)
}

foo('var a = "eval"') // eval
Copy the code

If we follow the lexical scope model to analyze the above code, only the global scope should have a variable a with a value of global at the lexical stage. But when foo is run eval (…) The function executes the var a = “eval” string as if it were written here. Foo’s scope generates a variable a with the value eval, and eval is printed because of the masking effect of the variable.

with

The with keyword may be unfamiliar to everyone, but let’s take a look at some code to explain how it works.

function foo(obj) {
  with (obj) {
    a = 2}}var o = {
  a: 3
}

foo(o)

console.log(o.a) / / 2
Copy the code

In the code above, with causes the property A on o to be reassigned. In fact, with opens a new scope using the passed object and treats the property on the object as a variable in the current scope, which does not actually exist at the lexical stage.

With can create a new scope, where finding variables still follows the general rules, so we need to avoid generating global variables by mistake because of with. Modify the above example:

function foo(obj) {
  with (obj) {
    a = 2}}var o = {
  b: 3
}

foo(o)

console.log(o.a, a) // undefined 2
Copy the code

In with, variable A needs to be assigned, so the engine uses LHS query to find variable A. If variable A cannot be found from the scope generated by with until it reaches the global scope, the engine creates a new variable in the global scope and performs an assignment to it.

summary

Today we have learned what lexical scope is and introduced two ways to dynamically change lexical scope. Let’s make a brief summary of today’s content:

  1. Lexical scope is a working model of scope. Function and block scopes are defined at code writing time.
  2. Eval and WITH can change the scope dynamically while your code is running, but don’t use them in actual code. They can greatly affect the efficiency of your code.