As we said, scopes are essentially a set of rules. If we think about scope from the perspective of closures – compilation principles, we’ve laid out the “rule” from beginning to end. Now, what we want to discuss is the cause of this “rule”.

Lexical scope and dynamic scope

I believe many of you may have been confused by the title – scope is scope, “lexical”, “dynamic” these strange prefixes are what?

In fact, when we talk about the concept of “scope” in the context of the JavaScript language, we really don’t need to distinguish between “lexical” and “dynamic” because our JS scope follows the lexical scope model. Don’t panic when an interviewer throws out the term “lexical scope”, which refers to the JS scope you’re most familiar with.

At the language level, however, there are two main working models for scopes:

  • Lexical scope: also known as static scope. This is the most common type of scope model and the focus of our study
  • Dynamic scoping: Relatively unfashionable, but some languages do adopt dynamic scoping: Bash scripts, Perl, etc

To understand lexical scope itself, we have to look at it outside of the JS box and put it in the context of its opposite, dynamic scope. To make both concepts more intuitive, let’s go straight to a piece of code:

var name = 'xiuyan';

function showName() {
    console.log(name);
}

function changeName() {
    var name = 'BigBear';
    showName();
}

changeName(); // xiuyan
Copy the code

This is JS code, based on the closure – Understanding scope from the perspective of compilation principle. It is easy to say that the result of the JS scope is “Xiuyan”. This is because JS takes a lexical (static) scope, and the code runs through this variable location process:

  • Look for the local variable name in the function scope of the showName function
  • The value of name is xiuyan, so the result will print Xiuyan.

At this point, its scope relationship is shown as follows:

The run-time scope chain is as follows:

Here we define the scope as we write it (in the case of function definition, block scope as well as code block definition), depending on where you write it. The scope delineated like this follows the lexical scope model.

So what is dynamic scope? With dynamic scoping, the same piece of code will do the following:

  • Look for the local variable name in the function scope of the showName function
  • Finding no showName, I continue to look for name along the function call stack where showName was called. Now let’s see where it goes. Did you find your way into changeName? Well, changeName has a name, so that name will be referenced in showName.

At this point, its scope chain relationship is shown as follows:

So if it was dynamically scoped, the code would run as’ BigBear ‘

To summarize, the difference between lexical and dynamic scopes is the timing of the scope division:

  • Lexical scope: When code is written, the scope chain extends outward along its defined location
  • Dynamic scope: When the code is partitioned at runtime, the scope chain extends out along its call stack

Modify the lexical scope

In relatively advanced front-end interviews, interviewers sometimes ask questions like: How do you “cheat” the lexical scope? Don’t be fooled by the fancy word “cheat,” which means change. When the interviewer asks you how to change your scope, he or she is not really expecting you to change the scope rules while writing code (which often costs performance), but is trying to figure out how well you know lexical scope. How to understand the action of “modify”? JS follows the lexical scope model is a foregone conclusion, can I change it to dynamic scope? Don’t say it. It’s good. Isn’t your JS scope only partitioned at writing time? So I have to change the scope of your partition during the run – who is so cool? Let’s bring up eval and with

Eval changes to a scope

function showName(str) {
  eval(str)
  console.log(name)
}

var name = 'xiuyan'
var str = 'var name = "BigBear"'

showName(str) The BigBear / / output
Copy the code

As you know, the input to eval is a string. When eval takes a string as an entry, it treats the contents of the string as a piece of JS code (whether or not it is a piece of JS code) and inserts it into the position where it was called. So in the above example, the showName function “transformed” by eval looks like this:

function showName(str) {
  var name = 'BigBear'
  console.log(name)
}
Copy the code

At this point, when we tried to output name, the name in the function scope had been modified by the line passed in to Eval, so the value of name in the function scope changed from ‘xiuyan’ to ‘BigBear’ (see the change eval made in the figure below). This change does occur only after eval (STR) is executed — eval changes the scope at runtime, successfully “modifying” the scope that was delimited at writing time under the constraints of the lexical scope rules.

With changes to scope

With may be a little stranger to you than Eval. It helps us “slack off” when we don’t want to prefix an object name repeatedly

var me = {
  name: 'xiuyan'.career: 'coder'.hobbies: ['coding'.'footbal']}// If we wanted to print a variable in the object me, we might do this without with:
console.log(me.name)
console.log(me.career)
console.log(me.hobbies)

// But with saves time writing prefixes
with(me) {
  console.log(name)
  console.log(career)
  console.log(hobbies)
}
Copy the code

Yes, with is a lazy way to refer to multiple attributes within an object. Why does with “change” the lexical scope? Let’s look at another example

function changeName(person) {
  with(person) {
    name = 'BigBear'}}var me = {
  name: 'xiuyan'.career: 'coder'.hobbies: ['coding'.'footbal']}var you = {
  career: 'product manager'
}

changeName(me)
changeName(you)
console.log(name) / / output 'BigBear'
console.log(me.name) / / output 'BigBear'
console.log(you.name) / / output is undefined
Copy the code

We were surprised to find that after executing changeName twice, we had a global variable named!

That’s with. With creates a new scope out of thin air. For example, the first time you execute changeName, it looks like this:

If we substitute the ability to create a new scope with with into the two changeName executions, it’s not hard to see why there is an extra global name. Here’s what happened:

  • The first changeName call, with, creates a new scope for the me object, allowing direct access to name, career, hobbies, and other object properties in this scope. The process is what we have in the picture above. So far so good.
  • The second changeName call, with, also creates a new scope for the you object, allowing direct access to the career object property in this scope (figure below).

It turns out we’re trying to access name — a variable that doesn’t exist in the current scope. So what happens? Note that with only changes the scope of the “create” action.When this scope is created, its query rules still follow the query rules of our lexical scope“, so it instinctively “pokes its head out” to its upper scope, the global scope, to search for name, and still cannot find it (see the scope chain below).

Note that we are in non-strict mode. In non-strict mode, the system will automatically create a name in the global scope for you, even if no name is found in the global scope. So name = ‘BigBear’ is executed smoothly, and the global variable name appears ~.

It all came out. Let’s quickly summarize how with changes scope:

  • With creates a new scope in place, and the set of variables in that scope is the set of properties of the target object passed in with.
  • Because the “create” action happens after the with code has actually been executed, the new scope is actually added at run time, and with thus implements the modification of the scope demarcated at writing time.

It is important to note that “change” simply describes the action of “create” — the new scope created. So its scoped query mechanism still follows the lexical scoped model.

Don’t write code in with and eval

Keep your head above water: We mention with and Eval here only to broaden your knowledge and make sure you don’t get caught up in the blind side of an interview, not to suggest the use of with and Eval. In fact, with and Eval have long been the bane of JS programmers’ minds because of their annoying side effects (such as a drag on language performance, global variables that “pop up” above us, etc.). No one actually uses it in the code, and I highly recommend not using it. During the interview, if the interviewer tries to ask a question such as “Tell me about your application of with and eval in a real project”, simply answer “I don’t write code with and eval”. Don’t worry about asking questions. Normal interviewers don’t ask questions. :)).