JavaScript基础-执行上下文

JavaScript的执行上下文定义

简单定义执行上下文是评估和执行JavaScript代码的环境的抽象概念。每当JavaScript代码在运行的时候,它都是在执行上下文中运行。

执行上下文的类型

  • 全局执行上下文=》默认基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的window对象(浏览器情况下),并且设置this的值等于这个全局对象。一个程序中只会有一个全局执行上下文中。
  • 函数执行上下文 =》每当一个函数被调用时,都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序执行一系列步骤。
  • Eval函数执行上下文 =》执行在eval函数内部的代码也会有它属于自己的执行上下文,但JavaScript开发者不经常使用eval,不讨论

 

执行栈

执行栈,也就是所说的“调用栈”,是一种拥有LIFO后进先出数据结构的栈,被用来存储代码运行时创建的所有执行上下文。也叫执行上下文栈【管理执行上下文】

// 为了模拟执行上下文栈的行为,让我们定义执行上下文栈是一个数组:
let ECStack = [];
// 试想当 JavaScript 开始要解释执行代码的时候,最先遇到的就是全局代码,所以初始化的时候首先就会向执行上下文栈压入一个全局执行上下文,我们用 globalContext 表示它,并且只有当整个应用程序结束的时候,ECStack 才会被清空,所以程序结束之前, ECStack 最底部永远有个 globalContext:
ECStack = [
  globalContext
];

function first() {
  console.log('Inside first function');
  second();
  console.log('Again inside first function');
}

function second() {
  console.log('Inside second function');
  third()
}
function third(){
  console.log("third function");
}

first();
console.log('Inside Global Execution Context');

// 当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。知道了这样的工作原理,让我们来看看如何处理上面这段代码:
/* 
// 伪代码
// first()
ECStack.push(<first> functionContext);

// first中竟然调用了second,还要创建second的执行上下文
ECStack.push(<second> functionContext);

// 擦,second还调用了third!
ECStack.push(<third> functionContext);

//third执行完毕
ECStack.pop();

// second执行完毕
ECStack.pop();

// first执行完毕
ECStack.pop();
// javascript接着执行下面的代码,但是ECStack底层永远有个globalContext
*/
/* 
                  Inside first function
js执行上下文.js:15 Inside second function
js执行上下文.js:19 third function
js执行上下文.js:11 Again inside first function
js执行上下文.js:23 Inside Global Execution Context
*/

怎么创建执行上下文

创建执行上下文有两个阶段:1、创建阶段【进入执行上下文】 2、执行阶段【代码执行

1、创建阶段

在JavaScript代码执行前,执行上下文将经历创建阶段。在创建阶段会发生三件事:

  1. this值得决定,即我们所熟知得this绑定
  2. 创建词法环境组件
  3. 创建变量环境组件

this绑定:

全局执行上下文中,this的值指向全局对象【在浏览器中,this引用window对象】;

函数执行上下文中,this的值取决于该函数是如何调用的。引用对象调用,指向这个引用对象;全局调用指向全局对象window或者underfined(在严格模式下);new实例对象指向这个new对象;call、bind、apply调用指向call、bind、apply函数的第一个参数对象;

词法环境

变量环境

进入执行上下文时,还没有执行代码,变量对象会包括:

1、函数的所有形参(如果是函数上下文)

  • 由名称和对象值组成的一个变量对象的属性被创建[var/let/const定义变量],如 var a = 1;let b=2
  • 没有实参,属性值设为【underfined】,let 定义变量没有先声明就引用会报错,没有initialization

2、函数声明

  • 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
  • 如果变量对象已经存在相同名称的属性,则完全替换这个属性

3、变量声明

  • 由名称和对应值(undefined)组成一个变量对象的属性被创建;,let 定义变量没有先声明就引用会报错,没有initialization
  • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
//看一个代码案例:
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
 var g = 20;
 return e * f * g;
}
c = multiply(20, 30);
//执行上下文看起来像这样
GlobalExectionContext = {

  ThisBinding: <Global Object>,

  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 在这里绑定标识符
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: < func >
    }
    outer: <null>
  },

  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 在这里绑定标识符
      c: undefined,
    }
    outer: <null>
  }
}

FunctionExectionContext = {
  ThisBinding: <Global Object>,

  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // 在这里绑定标识符
      Arguments: {0: 20, 1: 30, length: 2},
    },
    outer: <GlobalLexicalEnvironment>
  },

VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // 在这里绑定标识符
      g: undefined
    },
    outer: <GlobalLexicalEnvironment>
  }
}

注意 — 只有遇到调用函数 multiply 时,函数执行上下文才会被创建。
可能你已经注意到 let 和 const 定义的变量并没有关联任何值,但 var 定义的变量被设成了 undefined。
这是因为在创建阶段时,引擎检查代码找出变量和函数声明,虽然函数声明完全存储在环境中,但是变量最初设置为 undefined(var 情况下),或者未初始化(let 和 const 情况下)。
这就是为什么你可以在声明之前访问 var 定义的变量(虽然是 undefined),但是在声明之前访问 let 和 const 的变量会得到一个引用错误。
这就是我们说的变量声明提升。

2、执行阶段

在此阶段完成对所有这些变量的分配,最后执行代码。

注意 — 在执行阶段,如果 JavaScript 引擎不能在源码中声明的实际位置找到 let 变量的值,它会被赋值为 undefined

理解 JavaScript 中的执行上下文和执行栈

JavaScript深入之变量对象

https://juejin.im/post/5e8b163ff265da47ee3f54a6

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章