preface

Why am I writing this article? Maybe more people will see it if we change the familiar topic. I sent a TLS before and felt that the reading volume was not good.

Ecma grammar? I think or not, after all, these tedious, boring, and low threshold.

What’s the point? I’m going to tell you a little bit about something that I think we all know, but maybe we don’t quite understand.

Do I understand myself properly? I don’t think it’s perfect, but it’s definitely worth thinking about.

This is a series? It could be a series, starting with execution context and run.

Is js difficult? Look at your own goals. I think nothing is easy. The more you think about it, the more you see, the harder it is to understand.

Let’s get started

The body of the

Js or ECmascript?

You use JS for so long, have you figured out the difference between specification and implementation? ECMA is the organization that defines the specification for this language, and Javascript is an implementation of this specification. This means that there are many possible implementations, but Javascript is one of the most popular. When we talk about the ECMA specification, that’s an oral protocol specification, and when we talk about the javascript language, that’s a language that already implements one of the ECMA specifications.

On this basis, the execution context is an abstract concept mentioned in the ECMA specification. This means that it is not something that has been implemented, it is just an abstract model of how it is compiled and executed inside the computer and presented in object-oriented code. That should be the content of the details of the engine (V8) implementation.

The point of execution context, then, is that it gives an abstract model that makes it easier to predict how JS will work. At the same time, the execution context is of profound significance to the subsequent understanding of JS memory, garbage collection, closures and so on. It can help us analyze memory and execute process without understanding the basic underlying situation very much.

How does the JS code work?

In order not to complicate our thinking, we can temporarily divide the JS running process into three big steps.

  1. Get the JS code
  2. compile
  3. run

Compile phase: JS code is compiled into machine-readable executable code at compile phase (serialization > Abstract syntax tree > executable code)

Run: Run code

Execution Context

Execution ContextIs an abstract concept proposed in section 8 of ECMA Specification 262. This concept defines the context in which the JS code is run. In simple code, we can simply understand the context structure by:Lexical environment“(Lexical Environments) andThe variable environment(Variable Environment) two parts. We only need to focus on these two parts here:Lexicality: The lexicality environment defines the relationships that correspond to the ECMA specification’s lexicality during code compilation, such as recording the this content inside a function, which can be understood as its own syntactic relationships within ECMA.

Variable environment: Variable environment refers to the relationship of variables generated at runtime in the lexical environment, which can be understood as the variables we create.

In addition, the code we write, including code execution in functions, is called executable code in the specification. Therefore, we can summarize the running process of the code in more detail as, then the execution context and executable code will accompany the running cycle of JS:

In js, there are three comparison scenarios that generate context objects:

  1. Global context
  2. Function context
  3. The eval context

As a result, JS only generates execution context in three environments, which means that JS does not have the concept of a single block scope as c does, only a function scope and a global scope

The generation time of the execution context

Above we abstracted the code process into compile time and run time. The execution context determines the context at compile time, so it can be assumed that during compilation, when parsing the lexical relationship corresponding to THE JS code, the compiler has already determined the corresponding execution context relationship for each environment in the code, but it is not activated at this time. Although there is no mention of scope chains here, we usually refer to this relationship, which is determined at the lexical stage, as static. JS is often referred to as lexical or static scope because there are scope chains in the execution context.

This means that JS does a lot of things at compile time, including variable enhancement. Let’s see how this would work in real code:

Run procedures and call stacks

Since the execution context is added, it must be closely related to the execution time. I’m sure most of you know what we call the function call stack. This function call stack is essentially the call stack of the execution context. As we mentioned above, there’s also a global environment that generates a global context, and an eval environment that generates an Eval context. So, all of these contexts go into the call stack when activated.

The global context, for example, is pushed as soon as the code is compiled and run, because the global context is run first.

function a(){}
function b(){}

a()
b()
Copy the code

For the global environment, the executable code is shown. At runtime, memory should actually exist as machine code. When run to a(), the a function to the execution context is generated and then pushed.

View variable promotion from execution context

Variable promotion is a content we often pay attention to, we usually interpret variable promotion as, in the JS precompilation stage will do a promotion to the variable, here can use a simple demo to reproduce this classic phenomenon:

console.log(val) // undifined
add(1.2) / / 3
function add(v1, v2) {
    return v1 + v2
}

var val = 1
Copy the code

As you can see, there is no problem using it before the variable declaration. This code should come as no surprise to anyone who uses JS regularly.

But if we think a little deeper, what is the nature of variable ascension. Let’s recall the js runtime above. From a piece of JS code, compiled into executable code. Taking this code into the flow, I can further abstract the above code like this:

As shown in the figure, the above JS script code, through lexical parsing, the compiler will confirm that the code has two different context environments, and the corresponding content in each environment has also been marked. For example, in the global context, the corresponding executable code would be:

console.log(val)
add(1, 2)
var val = 1
Copy the code

The corresponding environment variables are the val and add function Pointers, which correspond to the executable code in the static code area. It is actually the corresponding executable code in the context of the function.

So at run time, the global context first activates the push, and then the global script code starts executing:

When we move to console.log, we see that, although val is behind console.log in our script code, I therefore pay instead of reporting an error. This is due to the lexical environment. Js generates the corresponding variable for us at compile time, but it does not correspond to the value yet.

Then when the cursor executes to add(1, 2), since the function variable has also been generated, and since the function declaration form. So the compiler knows that the function corresponds to the pointer to the executable code, so it calls the function, then activates the function into the context, and pushes it. The call stack is shown above. The add function variable is generated at compile time, thanks to lexical parsing, even though the function is executing the code later in the code.

Finally, when val = 1 is executed, the function exits the stack and the variable environment is assigned to val.

I need to say here, just so you can see it, that I’m showing the execution code in JS. But the actual compiled execution code should be machine code. As you can see in the figure, the variables val and add are undefined and a reference to the function, respectively.

At this point, I’m sure it should be easy to understand why variable promotion exists. In essence, the relationship between the execution context has been generated in the lexical parsing stage of JS compilation process, so the variable environment has been created before the code is run, while the code is running. Even if our executing code precedes the variable, we can still get a reference to the variable, and the context object is only activated when the code is running.

So the point of this chapter is that context objects are generated in the lexical phase, and context objects are activated in the run phase

The eval environment

When Eval code is run, the context has one more reference to the context in which the call is made.

The problem of variable promotion

Variable promotion can be considered as some of the shortcomings of the original JS design, because from the above description, this simple design led to variable promotion. This promotion has some effect in a number of possible block scopes. Like a while for loop. For those who have worked with languages like C or Java, the simple function-scoped block nature of JS can be hard to understand. The promotion of a variable in a for loop or in a while causes the variable to be overwritten globally and cannot be cached.

Of course, es6 also has the concepts of let and const to solve the problem of block scope. But essentially, variable promotion is not a very good feature.

Finally, you can try to think about the multi-nature of closures in terms of context objects, which we’ll talk about later.