JS 代码执行过程

150 阅读3分钟

《用得上的前端知识》系列 - 你我都很忙,能用100字说清楚,绝不写万字长文

基本概念

执行上下文:可以理解为 JavaScript 执行一段代码时的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,确定该函数在执行期间用到的诸如 this、变量、对象以及函数等。

代码的执行与上下文的创建

JS 代码的执行过程:

  • 编译阶段,通过“词法分析”和“语法分析”构建出源码对应的抽象语法树(AST);
    • 词法分析,把源代码作为一段字符串,分解成若干有意义的代码字符(也叫“词法单元”,token),随后生成一个词法单元对象集合 tokens,每个词法单元对象都包含了该 token 的类型(type)和值(value);
    • 语法分析,根据 tokens 中各 token 之间的关系形成反映整个程序语法的树状结构,也就是语法树 AST。
  • 预处理阶段
    • 根据已构建的 AST 创建 EC(执行上下文),同时进行标识符的绑定与初始化;
    • 根据 AST 生成字节码。
  • 执行阶段。

执行上下文的创建

  • 通过“词法分析”和“语法分析”构建 抽象语法树(AST);
  • 根据 AST 创建 执行上下文
  • 创建一个新的 词法环境对象
  • 将该执行上下文的 变量环境组件(VariableEnvironment) 和 词法环境组件(LexicalEnvironment) 都指向新创建的 词法环境对象
  • 将创建的执行上下文 推入执行上下文栈 并成为 正在运行的 执行上下文
  • 对代码块内的标识符进行绑定及初始化;
  • 运行代码;
  • 运行完毕后 执行上下文 出栈;
  • 继续执行 执行上下文栈 栈顶的上下文对应的代码,直至所有 执行上下文 对应的代码被执行完毕。

注意:只有当整个应用程序结束的时候,调用栈才会被清空,所以程序结束之前, 调用栈最底部永远有个全局执行上下文

标识符绑定及初始化

  • 将码块内的let、const和class声明的标识符合集记录为lexNames;
  • 将代码块内的var和function声明的标识符合集记录为varNames;
  • 如果lexNames内的任何标识符在varNames或lexNames内出现过,则报错SyntaxError;
  • 将varNames内的var声明的标识符进行绑定并初始化为 undefined,如果有同名标识符则跳过;
  • 将lexNames内的标识符进行绑定,但其值并不会进行初始化,在运行至其声明处代码时才会进行初始化,在初始化前访问都会报错(暂时性死区);
  • 最后将varNames内的函数声明进行标识符绑定并初始化赋值对应的函数体。
    • 如果有同名函数声明,则前面的都会忽略,只有最后一个声明的函数会被初始化赋值;
    • 如果变量与函数同名,则函数声明将覆盖变量声明。

举例说明

let a = '面包';
var b = '可乐';
// (1)
if( true ){    
    const c = '汉堡';  // (2)
}

function helloFn(){    
    var d = '鸡翅';  // (3)
}

fn();

随着代码的编译、执行,执行上下文 中标识符的绑定和赋值情况如图中黄色卡片的标注:

参考资料