1024programmer Java A deep dive into the most fundamental part of JavaScript: execution context

A deep dive into the most fundamental part of JavaScript: execution context

In this article, I will delve into one of the most fundamental parts of Javascript, the Execution Context. By the end of this article, you should understand the interpreter better: why are certain functions or variables available before declaring them? And how are their values ​​determined?

What is execution context?

The execution environment of Javascript is very important. When the Javascript code is running, it will be preprocessed into one of the following situations:

  • Global code – the default environment for first execution of code.
  • Function code – whenever execution flow enters the function body.
  • Eval code – text to be executed within the eval function.

You can read a lot of information online about scope, but to make things easier to understand, let’s think of the term “execution context” as the execution environment or scope of the current code. Next let’s look at a code example that includes global and function/local contexts.

Nothing special here, we have a global context represented by a purple border, and 3 different function contexts represented by green, blue and orange borders. There can be only 1 global context, accessible from any other context in the program.

You can have any number of function contexts, and each function call creates a new context, creating a private scope where anything declared inside the function cannot be directly accessed from outside the current function scope. In the above example, the function can access variables declared outside its current context, but the external context cannot access variables or functions declared within it. Why is this so? How exactly does this code work?

Execution Context Stack

The Javascript interpreter in the browser is implemented as a single thread. In practice this means that only one thing can be done at a time in the browser, with other actions or events queued up in what is called the execution stack. The following figure is an abstract view of a single-thread stack:

We already know that when a browser loads a script for the first time, it defaults to the global context for execution. If a function is called in global code, the sequence flow of the program goes into the called function, creating a new execution context and pushing it to the top of the execution stack.

The same thing happens if another function is called within the current function. The code’s execution flow enters an inner function, which creates a new execution context, which is pushed onto the top of the existing stack. The browser will always execute the current execution context at the top of the stack, and once the function has finished executing the current execution context, it will pop off the top of the stack, returning control to the next context in the current stack. The following examples show the execution stack of recursive functions and programs:

 (function foo(i) {
   if (i === 3) {
     return;
   }
   else {
     foo(++i);
   }
 }(0));

The code simply calls itself 3 times and increments the value of i by 1. Each time function foo is called, a new execution context is created. Once the context completes execution, it pops the stack and returns control to the context below it until the global context is reached again.

There are 5 key points about the execution stack:

  1. Single threaded.
  2. Synchronized execution.
  3. A global context.
  4. Any number of function contexts.
  5. Each function call creates a new execution context, even a call to itself.

Execution context details

So we now know that every time a function is called, a new execution context is created. However, in the Javascript interpreter, every call to the execution context has two phases:

Creation phase [when a function is called, but before any code is executed]:

  1. Create a scope chain.
  2. Create variables, functions and parameters.
  3. Determine the value of “this”.

Activation/code execution phase:

  • Assign values, reference functions and interpret/execute code.

Each execution context can be conceptually represented as an object with 3 properties:

 executiOnContextObj= {
   'scopeChain': { /* variableObject + variableObject of all parent execution contexts */ },
   'variableObject': { /* Function parameters/formal parameters, internal variables and function declarations */ },
   'this': {}
 }

Activation Object/Variable Object [AO/VO]

This executionContextObj will be created before the function is called and before the function is actually executed. This is called Phase 1, the Creation Phase. At this time, the interpreter scans the actual parameters or formal parameters passed by the function, local function declarations andLocal variable declaration to create executionContextObj. The result of this scan will become a variableObject in executionContextObj.

The following is a pseudocode overview of how the interpreter preprocesses code:

1. Find some code to call a function.

2. Before executing function code, create an execution context.

3. Enter the creation stage:

①Initialize the scope chain.

②Create variable object:

  • Create arguments object, check the context of parameters, initialize names and values ​​and create reference copies.
  • Scan the context for function declarations:
  • For each function found, create a property in the variable object that is the exact name of the function that holds a reference pointer to the function in memory.
  • If the function name already exists, the reference pointer value will be overwritten.
  • Scan the context for variable declarations:
  • For each variable declaration found, create a property in the variable object as the variable name and initialize the value to undefined.
  • If the variable name already exists in the variable object, do nothing and continue scanning.

③ Determine the value of “this” in the context.

4. Activation/execution phase:

  • Run/interpret function code in context and assign variable values ​​as the code is executed line by line.

Let’s look at an example:

 function foo(i) {
   var a = 'hello';
   var b = function privateB() {
   };
   function c() {
   }
 }
 foo(22);

When foo(22) is called, the creation phase looks like this:

 fooExecutiOnContext= {
   scopeChain: { ... },
   variableObject: {
     arguments: {
       0:22,
       length: 1
     },
     i: 22,
     c: pointer to function c()
     a: undefined,
     b: undefined
   },
   this: { ... }
 }

As you can see, the creation phase handles defining the names of properties rather than assigning values ​​to them, with the exception of formal parameters/arguments. After the creation phase is completed, the execution flow enters the function, and the activation/code execution phase is as follows after the function execution is completed:

 fooExecutiOnContext= {
   scopeChain: { ... },
   variableObject: {
     arguments: {
       0:22,
       length: 1
     },
     i: 22,
     c: pointer to function c()
     a: 'hello',
     b: pointer to function privateB()
   },
   this: { ... }
 }

About hoisting

You can find many online resources that define the term hoisting using Javascript, explaining that variable and function declarations are hoisted to the top of their function scope. But no one has been able to explain in detail why this happens, and armed with new knowledge about how the interpreter creates activation objects, it’s easy to understand why. Please see the code example below:

 (function() {
   console.log(typeof foo); // function pointer
   console.log(typeof bar); // undefined
   var foo = 'hello',
     bar = function() {
       return 'world';
     };
   function foo() {
     return 'hello';
   }
 }());

The questions we can answer now are:

Why can we access foo before declaring it?

  • If we understand the creation phase, we know that variables are created before the activation/code execution phase. Therefore, when the function flow starts executing, foo is already defined in the activation object.

Foo is declared twice, why does foo appear as function instead of undefined or string?

  • Even if foo is declared twice, we know through the creation phase that the function is created on the activation object before the variable, and if the property name already exists on the activation object, we just bypass the declaration step.
  • So first a reference to the function foo() is created on the activation object and when the interpreter reaches var foo we have already seen that the property name foo exists so the code does nothing and continues processing.

Why is bar undefined?

  • bar is actually a variable with function assignment. We know that variables are created during the creation phase, but they are initialized with undefined values.

Hopefully by now you have a good grasp of how the Javascript interpreter preprocesses your code. Understanding the execution context and stack allows you to understand why the preprocessed value of your code is different from what you expected.

Do you think learning the inner workings of an interpreter is unnecessary or necessary? Can understanding the execution context phase help you write better Javascript?

Summary

The above is the entire content of this article. I hope that the content of this article has certain reference and learning value for everyone’s study or work. Thank you for your support. If you want to know more related content, please check the relevant links below

This article is from the internet and does not represent1024programmerPosition, please indicate the source when reprinting:https://www.1024programmer.com/714795

author: admin

Previous article
Next article

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact Us

Contact us

181-3619-1160

Online consultation: QQ交谈

E-mail: [email protected]

Working hours: Monday to Friday, 9:00-17:30, holidays off

Follow wechat
Scan wechat and follow us

Scan wechat and follow us

Follow Weibo
Back to top
首页
微信
电话
搜索