Please indicate the original link for reprinting. The original link

The handwritten interview questions series is a series of essays I wrote to prepare for the present and future interviews, of course, it is also helpful for front-end partners. I recommend typing the code yourself or writing it by hand to get a better grasp of it.

References:

  1. Understand the execution context and execution stack in JavaScript
  2. Lexical Environment — The hidden part to understand Closures
  3. Lexical context — hidden corners of closures

Execution context and execution stack are relatively simple concepts in JavaScript, but they involve lexical environments, closures, and so on. This article mainly introduces the concepts of execution context and execution stack, and illustrates them with practical examples. Finally, some knowledge about lexical environment will be mentioned. Since lexical environments are closely related to closures, this article will give a brief introduction to lexical environments, and the next article will focus on lexical environments and closures.

Execution context

1. What is execution context?

An execution context is an abstraction of the context in which JavaScript code is evaluated and executed. Whenever Javascript code is running, it is running in an execution context.

In short, the execution context is the context in which the code in JavaScript runs. For example, we know the global environment, also called the global scope, function inside or function scope, etc. Execution contexts in JavaScript can be divided into three categories, based on the context in which the code is executed.

  • Global execution Context – Any code that is not inside a function is in the global context. It does two things: it creates a global Window object (in the case of the browser) and sets the value of this to equal the global object. There is only one global execution context in a program.
  • Function execution Context – Each time a function is called, a new context is created for that function. Each function has its own execution context, but is created when the function is called. There can be any number of function contexts. Each time a new execution context is created, it performs a series of steps in a defined order (discussed later).
  • Eval function execution Context – Code executed inside the Eval function also has its own execution context, but since Eval is not often used by JavaScript developers, I won’t discuss it here.

Execution stack

An execution stack, or “call stack” in other programming languages, is a stack with LIFO (LIFO) data structures that are used to store all execution contexts created while the code is running. When the JavaScript engine first encounters your script, it creates a global execution context and pushes it onto the current execution stack. Every time the engine encounters a function call, it creates a new execution context for that function and pushes it to the top of the stack. The engine executes functions whose execution context is at the top of the stack. When the function completes execution, the execution context pops out of the stack and the control flow moves to the next context in the current stack.

For example:

Function foo() {console.log(' first time inside function foo '); foo1(); Console. log(' second time inside function foo '); } function foo1() {console.log(' inside function foo1 '); } foo(); Console. log(' global execution context ');Copy the code

Most readers are familiar with the order in which the code outputs, and the introduction of the execution stack can help us understand the order in which the code runs.

The output of the above code is in the following order:

The first time inside function foo inside function foo1 and the second time inside function foo global execution contextCopy the code

We use the concept of an execution stack to describe the order in which code is run:

  1. theGlobal execution contextInto the stack
  2. Define function foo() and function foo1();
  3. Run the function foo() and put theFunction execution contextInto the stack;
  4. Go inside the function foo() and run the codeConsole. log(' first time inside function foo ');And prints “first time inside function foo”;
  5. Execute function foo1() and place the values of function foo1()Function execution contextPush it inside the function foo1() and run the codeConsole. log(' inside function foo1 ');And the outputInside the function foo1Foo1 () internal code all run, put function foo1()Function execution contextOut of the stack;
  6. When foo1() is finished, continue to run the codeConsole. log(' second time inside function foo ');And the outputThe second one is inside foo, function foo() completes, and puts theFunction execution contextOut of the stack, backGlobal execution context.
  7. Go back toGlobal execution context, run the codeConsole. log(' global execution context ');And the outputGlobal execution context.Global execution contextWhen all the code is executed, put theGlobal execution contextOut of the stack, the program run end;

Using the concept of execution stack, you can clearly understand the sequence of the above code. Let me show you how to create an execution context.

How to create execution context?

There are two phases to creating an execution context: the creation phase and the execution phase.

The meaning of the execution phase is very simple, is in order to run the code from the top, the concept of running the execution stack is a good analysis of the code execution order. Here, I’ll focus on what the JavaScript engine does during the creation phase.

During the creation phase, the JavaScript engine does three things:

  1. The determination of this value, known as the this binding;
  2. Create lexical environment components;
  3. Create variable environment components;

The concept of an execution context can be expressed as:

ThisBinding = <this value>, // this binding LexicalEnvironment = {... }, // lexical environment VariableEnvironment = {... }, // Variable environment}Copy the code
1. This binding

Readers of my last article will understand the meaning of the this binding, which refers to an object. In the context of global execution, this refers to the global object; in the browser, the window object; In the context of function execution, the reference to this depends on how the function is called.

2. Lexical environment

Many readers may be unfamiliar with the concept of lexical environment, so let me explain.

Lexical environment consists of two parts, one is the environment logger, and the other is the reference to the external environment.

The environment logger is the actual location where variable and function declarations are stored; A reference to an external environment means that it has access to its parent lexical environment (scope);

In layman’s terms, in a lexical context, the context logger records the actual values of variables and functions in the execution context, and references to the external context allow that execution context to access the parent scope along the scope chain.

3. Variable environment

In ES6, the context loggers of the lexical environment are used to store functions and variables declared with let and const, while the context loggers of the variable environment are used to store variables declared by var.

Comb through the steps to create the execution context

With these concepts in mind, let’s review how to create a global execution context and how to create a function execution context.

1. Steps to create a global execution context
  1. The this binding points this in the global execution context to the window object.
  2. To determine the lexical environment, store all function declarations and variables using let and const declarations in the global execution context to the lexical environment logger, and point the reference of the global execution context to the external environment to NULL;
  3. Determine the variable environment: store variables declared by var in the global execution context to the environment logger of the variable environment, and initialize the value of these variables to undefined;

When we create the global execution context, we determine the pointing problem of this, record all function declarations in the global execution context, record all variable declarations (including those declared with let, const, var), where the var variable is initialized to undefined. That is, the JavaScript engine does this before a line of code runs, before the global execution context is pushed onto the execution stack;

2. Steps to create a function execution context
  1. This binding, in the context of function executionthisDepending on how the function is called, is the pointer in the code that has been identified each time the function is called before the code is runthisThe point;
  2. Determines the lexical context, and stores all function declarations and variables that use let and const declarations in the context of function execution into the lexical context’s context loggerargumentsAn environment logger that stores objects in lexical environments and points to references to the external environment from the function execution context;
  3. Determine the variable environment: store the variables declared by var in the function execution context in the variable environment logger, and initialize the value of these variables to undefined;

Reading through the creation step of the function execution context, the reader can see some differences between it and the global execution context creation step.

  1. Functions can be called multiple times, so the same function called multiple times will have multiple function execution contexts. The this of each function execution context may not be the same, but will be determined before the code runs.
  2. The variables in the lexical environment of a function also contain parameters passed to the functionargumentsObject, which also indicates that the parameters of a function can be understood as internal variables declared inside the function, as well as function variables defined inside the function;
  3. The lexical context of a function’s execution context also contains references to the external context (including references to the parent scope), which means that the parent scope that the function can access is already defined before the code is executed (think closures?). .

Here, AS an example, the reader can think about the execution result;

var b = 10; function foo(a) { a = 1; console.log(a, b); } function foo1() { b = 100; foo(b); } foo(1000); / / output? foo1(); / / output?Copy the code

The correct answer is 1, 10, 1, 100

The first output is when foo(1000) is run. If the passed parameter 1000 does not take effect, the parameter a is assigned twice inside the function, so no matter what parameter is passed in, the output value of A cannot be changed to 1. The value of b is 10 because a reference to the external context in the lexical context of function foo() refers to the global context, so function foo looks for variable B in the global context when there is no variable b in it.

The second output is when you run foo1(). Foo1 () calls foo internally, passing in 100. Passing 100 does not change a’s output value to 1. Foo1 () runs inside code b = 100; This is actually equivalent to window.b = 100; , changes the value of b in the global execution context, resulting in the output value of b being 100; Here, even after foo() is run, the output value will always be 1, 100, because the value of b in the global execution context has been permanently changed.

The above example is interspersed with the knowledge referred to in the function this, if the reader can not fully understand my last article: handwriting interview question 3: in-depth understanding of js this binding). Stay tuned for my next article, which will examine lexical environments, closure concepts, and usage in detail.