JavaScript的代码运行机制

2,168 阅读5分钟


这是一张简单的JavaScript运行图(如有错误地方请指出,谢谢大家)。大致分为两个阶段,编译阶段和执行阶段。在上一篇文章【JavaScript变量提升运行机制】中有简单提到过。这篇文章带大家来了解其中的一些概念。

一、编译阶段

分词/词法分析

这个过程是将由字符组成的字符串分解为有意义的代码块,这些代码块我们称之为词法单元(token)。例如:var num = 1;在当前阶段会被分解为var、num、=、1、空格,每一个都是一个词法单元,当然空格是否是一个有效的词法单元取决于空格是否在JavaScript是否有意义。

解析/语法分析

这个过程主要是将词法单元流(数组)转换为一个由元素嵌套的程序语法树(抽象语法树,AST)。

预解释/代码生成

这个阶段主要是将AST转换为可执行代码。这个阶段会进行变量的提升

引入几个简单的概念:

引擎:负责整个JavaScript的编译和执行过程。编译器:负责JavaScript的语法分析和代码生成。作用域:负责收集并维护所有申明的标识符组成的一系列查询,并实施一套规则,确定当前执行的代码对这些标识符的访问权限。

二、执行阶段

1、可执行代码

JavaScript并不是简单的一行行解释执行,而是将JavaScript代码分为一块块的可执行代码块进行执行,JavaScript中主要主要分为三类可执行代码。

  • 全局可执行代码
  • 函数可执行代码
  • Eval可执行代码

2、JavaScript引擎


上图描述了JavaScript的执行过程,具体过程我们先不看。一个JS引擎主要有一下几部分组成。

  • 编译器:负责JavaScript的语法分析和代码生成。
  • 解析器:在某些引擎中,解释器主要是接收字节码,解释执行这个字节码,同时也依赖垃圾回收机制等。
  • JIT:将字码节或者抽象语法树转换为本地可执行代码。
  • 垃圾回收,分析工具:负责垃圾回收和收集引擎中的信息,帮助改善引擎的性能和功效。

3、JavaScript引擎的内存堆(emory Heap)和调用栈(Call Stack)

内存堆(emory Heap):分配内存地址

调用栈(Call Stack):代码执行

    let name = '蜗牛';

    function sayName(name) {
        sayNameStart(name);
    }
    function sayNameStart(name) {
        sayNameEnd(name);
    }
    function sayNameEnd(name) {
        console.log(name);
    }

当代码进行声明时


执行sayName函数时,会把直接函数压如执行栈,并且会创建执行上下文



4、执行上下文

JavaScript中每一个可执行代码,在解释执行前,都会创建一个可执行上下文。按照可执行代码块可分为三种可执行上下文

  • 全局可执行上下文:每一个程序都有一个全局可执行代码,并且只有一个。任何不在函数内部的代码都在全局执行上下文。
  • 函数可执行上下文:每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都被调用时都会创建它自己的执行上下文。
  • Eval可执行上下文:Eval也有自己执行上下文。


5、执行上下文的创建

  • this的指向:除开箭头函数的this是编辑阶段确定的之外,其他this都是在代码执行阶段【代码执行阶段 == 执行上下文创建阶段】确认的。

1、普通函数的调用:this指向window(浏览器环境)
2、对象方法的调用:this指向调用对象
3、构造函数:this指向构造函数实例
4、applycallbindthis指向绑定值
5、箭头函数thisthis指向外层第一个普通函数调用的this

  • 创建词法环境
  1. 全局环境:全局环境的外部环境引用是 null,它拥有内建的 Object/Array/等、在环境记录器内的原型函数(关联全局对象,比如 window 对象)还有任何用户定义的全局变量,并且 this的值指向全局对象。
  2. 模块环境:包含模块顶级声明的绑定以及模块显式导入的绑定。 模块环境的外部环境是全局环境。
  3. 函数环境:函数内部用户定义的变量存储在环境记录器中,外部引用既可以是其它函数的内部词法环境,也可以是全局词法环境

词法环境本身包括两个部分:

  1. 『环境记录器(Environment Record)』是存储变量和函数声明的实际位置
  2. 『外部环境的引用(outer Lexical Environment)』指它可以访问其父级词法环境(即作用域)

对于『环境记录器』而言,它又分为两个主要的环境记录器类型:

  1. 声明式环境记录器(DecarativeEnvironmentRecord):范围包含函数定义,变量声明,try...catch等,此类型对应其范围内包含的声明定义的标识符集
  2. 对象式环境记录器(ObjectEnvironmentRecord):由程序级别的(Program)对象、声明、with语句等创建,与称为其绑定对象的对象相关联,此类型对应于其绑定对象的属性名称的字符串标识符名称集
  • 创建变量环境:变量环境也是一个词法环境,但不同的是词法环境被用来存储函数声明和变量(let 和 const)绑定,而变量环境只用来存储 var 变量绑定。


6、代码执行

当浏览器加载某些JavaScript代码时,引擎会逐行读取并执行以下步骤:

  • 使用变量和函数声明填充全局内存(堆)
  • 将每个函数调用推送到调用堆栈
  • 创建全局执行上下文,其中执行全局函数
  • 创建许多微小的本地执行上下文(如果有内部变量或嵌套函数)


三、小结

通过这个文章我们可以简单的了解相关的JavaScript代码执行机制。

参考:JavaScript的运行机制