Execution Context EC

The JS controller enters the executable context (EC) when it goes to executable code. EC is an abstract concept that needs to be distinguished from the concept of executable code. The execution context of the activity forms a stack. The bottom of the stack is the global context, and the top is the currently active execution context. The context is entered, and the context is pushed onto the stack. Leaving the context, the context pops up the stack.

Global context

The initial context stack, the global context does not contain any function code.

#Execution context stack
ECStack = [
  globalContext,
]
Copy the code

Function code

Whenever a function is entered, ECStack is pushed into a new element (the execution context of the function), but not the context of nested functions that have not yet been executed. The execution context at the top of the ECStack is popped up when a return or exception does not catch. When all the code has been executed, there is only one global execution context in the ECStack.

function foo (trigger) {
  if (trigger) {
    return
  }
  // recursive call
  foo(true);
}

foo()
Copy the code
#Call foo for the first time
ECStack = [
  <foo>functionContext,
  globalContext,
]
#Foo is recursively called a second time
ECStack = [
  <foo>functionContext - recursion,
  <foo>functionContext,
  globalContext,
]
#Recursive functionreturnafter
ECStack.pop()
#After the foo function is executed
ECStack.pop()
#This is ECStack
ECStack = [
  globalContext,
]
Copy the code

Call context

The calling context is the calling context generated when the eval function is called. In Firefox, the uncle Tom blog says, eval is implemented as a second parameter that can be used to pass context, executing eval in a given context. But I tried it in Chrome, and it didn’t work.

eval('var x = 10');

(function foo() {
  eval('var y = 20'); }) ();Copy the code
ECStack = [globalContext]; ECStack = [ evalContext, callingContext: globalContext ]; ECStack.pop(); ECStack = [ callingContext: <foo> functionContext, globalContext ]; ECStack = [ evalContext, callingContext: <foo> functionContext, globalContext ]; ECStack.pop(); ECStack.pop(); ECStack = [ globalContext ];Copy the code

Variable object/active object

The variable object VO, which is a special attribute specific to the execution context, stores the following contents of the context:

  1. variable
  2. Declaration of functions (without function expressions)
  3. Parameter to a function
// VO is an attribute of the execution context
activeExecutionContext = {
  VO: {}}Copy the code

VO of the global context, which can be accessed directly by the VO attribute name. The global object itself is VO. VO objects are not directly accessible in other contexts

var a = 10;
function foo(x) {
  var b = 20;
};
foo(30);
Copy the code
#Context stack and VO objects
ECStack = [
  #Context for Foo
  functionContext: {
    VO: {
      x: 30,
      b: 20,
    }
  }
  #Global context
  globalContext: {
    VO: {
      a: 10
      foo: <reference to function>
    }
  }
]
Copy the code

Global context variable object

A global object is an object that is created before entering any context, and there is only one copy of a global object. Global objects are initialized with Math, String, Date, and parseInt as their properties during creation. Other global variables are treated as properties of the global object. VO of the global context is the global object itself GlobalContext.vo === global

// Global objects
global: {
  Math.Date.window: global // Reference itself
}
Copy the code

The variable object of the function context

VO is not directly accessible in the context of function execution. Replace the function of the variable object VO with the active object AO in the execution context of the function.

The AO is created when the function execution context is entered, initialized by the Arguments property, whose value is the Arguments object. The length property of the Arguments object is the number of Arguments, and callee is a reference to the current function.


AO = {
  arguments: Arguments
};
Copy the code

Process two phases of context code

In this section, we can see why the value of a variable in JS is undefined by default.

Executing the context code is processed in two stages:

1. Enter the execution context. 2

Phase: Enter the execution context

VO/AO contains the following attributes when the code enters the execution context, before execution begins

  • Parameter, which is an attribute of the AO object. If the attribute is passed, it will be assigned, otherwise it will be undefined.
  • Function declarations, the name of the function and the contents of the function, are used as the key and value of the variable object (function declarations take precedence over variable declarations, which supersede variable declarations if they have the same properties)
  • Variable declarations, which do not interfere if they have the same attributes as parameters and function declarations. (Variable declarations come after parameter and function declarations.)
function Foo (a, b) {
  var c = 10;
  var d = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
}

Foo(10);
Copy the code

The AO object is as follows. The AO object does not contain x functions, which are not function declarations but function expressions. X does not exist in VO/AO. Unsaved function expressions can only be called in their own execution context. Since function declarations take precedence, the D attribute of the VO variable object is currently a function, not a number, at the stage of entering the execution context.

// The live object of code entered into the execution context but not yet executed
AO = {
  a: 10.b: undefined.c: undefined.d: <reference to FunctionDeclaration "d">,
  e: undefined
}
Copy the code

Phase: Executes the code in context

Then enter the code execution phase, at this time the AO/VO object has the attribute, but the attribute value is still undefined, the VO attribute will be updated during the code execution phase.

// During code execution, the value of the AO active object is updated.
AO = {
  a: 10.b: undefined.c: 10.d: 10.// Attribute d will be assigned to 10 during the execution of the code
  e: undefined
}
Copy the code

So why is js variable default undefined? Because the VO/AO object’s property value is given undefined when entering the execution context, the js variable value is given undefined by default, not inherently undefined.

So the following code, even if it never reaches the branch of B, b will be assigned undefined because it was put into VO when it entered the execution context.

if (true) {
  var a = 1;
} else {
  var b = 2;
}
 
alert(a); / / 1
alert(b); // undefined
Copy the code

The scope chain

Nested functions are allowed in JS, and each function has its own variable object VO, which is an AO active object for functions since VO cannot be accessed directly. The scope chain is a list of AO’s. Scope chains are used to query variables.

The execution context scope chain of a function, created when the function is called, contains the VO/AO attribute, as well as the [[scope]] attribute. The scope chain is equal to the VO + [[scope]] property

ECStack = [
  functionContext: {
    VO: {},
    this: thisValue, [[scope]]: [...] .Scope: [...].// Scope chain (VO + [[scope]])}]Copy the code

[[scope]]attribute

The [[scope]] property contains the hierarchy of all parent variable objects (VO). [[scope]] is created and stored at function creation time, immutable. Until the function is destroyed, that is, the scope chain is defined when the function is defined. Even if the function is not called, its [[scope]] property is written.

function foo () {}

// Foo's '[[scope]]' property
// Contains the parent variable object, such as the VO of the global object
fooContext.[[scope]] = [
  globalContext.VO
]
Copy the code

Function is activated

When the AO/VO object is created in context, the AO/VO object adds the first part of the Scope attribute. This is important for identifier resolution, because the variable name is searched from the bottom. Since the current AO/VO object is first, the local variable will have higher priority than the variable in the parent Scope.

Scope attribute = [current execution context AO/VO, [[Scope]]]

Here’s an example

var x = 10;
function foo() {
  var y = 20;
  function bar() {
    var z = 30;
  }
  bar();
}
foo();
Copy the code

The execution context stack for the above function is as follows


ECStack = [
  <bar>functionContext: {
    [[scope]]: [
      <foo>functionContext.VO
      globalContext.VO
    ],
    VO: {
      z: 30
    },
    Scope: [
      <bar>functionContext.VO,
      <foo>functionContext.VO,
      globalContext.VO
    ]
  },
  <foo>functionContext: {
    [[scope]]: [
      globalContext.VO
    ],
    VO: {
      y: 20.bar: <reference to function>
    }
    Scope: [
      <foo>functionContext.VO,
      globalContext.VO
    ]
  },
  globalContext: {
    VO: {
      x: 10
      foo: <reference to function>
    },
  },
]
Copy the code

Eval and scope chains

The context of code Eval has the same chain of scopes as the current calling context

The impact of code execution on the scope chain

With and catch modify the scope chain during code execution and are added to the front of the scope

When confronted with or catch, Scope = [AO | VO, [[Scope]]] = = > [withObject | catchObject, AO | VO, [[Scope]]]

var foo = {x: 10.y: 20};
 
with (foo) {
  alert(x); / / 10
  alert(y); / / 20
}
Copy the code

The modified scope chain

Scope = [foo, AO|VO, [[Scope]]]

var x = 10, y = 10, z = 10;
 
with ({x: 20.z: 20{})// Add x to with
  // But y modifies the outside y
  var x = 30;
  var y = 30;
  
  // Here access with enhanced x and z
  alert(x); / / 30
  alert(y); / / 30
  alert(z); / / 20
}

// 
alert(x); / / 10
alert(y); / / 30
alert(z); / / 10
Copy the code

After with completes, its specific object is removed from the scope chain (the changed variables X and z are also removed from that object), and the structure of the scope chain is restored to the state it was before with was strengthened. So x, z becomes 10.

The catch statement also creates an object with a new attribute, which is the exception parameter name

try{... }catch (ex) {
  alert(ex);
}
Copy the code
var catchObject = {
  ex: <exception object>
};
 
Scope = catchObject + AO|VO + [[Scope]]
Copy the code

closure

Closures are a combination of a code block and the data in the context in which the code block was created

The parent context data of a function is stored in the function’s internal property [[scope]]. Since JS is a language that uses static lexical scope, the [[scope]] property containing the parent context data is stored when the function is created, whether the function is called or not.

Because of the scope chain, all functions in JS are closures, because when they are created, they hold the scope chain of the upper context.

var x = 10;

function foo() {
  alert(x);
}
// foo is a closure
foo: <FunctionObject> = {
  [[Call]]: <code block of foo>, [[Scope]] : [global: {x: 10 / / foo [[Scope]], contains the parent object variable}],};Copy the code

Quote the same[[scope]]

Closures created in the same context share a [[scope]] property, and changes to [[scope]] affect other closures. That is, closures created in the same context share the same parent scope.

var foo;
var bar;

function test() {
  var x = 1;
  foo = function () { return ++x; };
  bar = function () { return --x; };
  x = 2;
  / / 3
  alert(firstClosure());
}
test();
alert(foo()); / / 4
alert(bar()); / / 3
Copy the code

A classic example, the following example prints 3, because k is k in the same parent scope

var data = [];
for (var k = 0; k < 3; k++) {
  data[k] = function () {
    alert(k);
  };
}
data[0] ();/ / 3
data[1] ();/ / 3
data[2] ();/ / 3
Copy the code

Under modification, the printed result is 0,1,2, because the function expression returned by IFEE prints the property x of the active object for the _helper function. But if YOU print k, you still get 3.


var data = [];
for (var k = 0; k < 3; k++) {
  data[k] = (function _helper(x) {
    return function () {
      alert(x);
    };
  })(k);
}
data[0] ();/ / 0
data[1] ();/ / 1
data[2] ();/ / 2
Copy the code

reference

  • Uncle Tom’s blog: An in-depth understanding of JavaScript series