JavaScript 执行机制学习笔记

82 阅读5分钟

JavaScript 执行机制学习笔记

本文基于 V8 引擎视角,系统梳理 JavaScript 的执行流程、执行上下文、变量提升、作用域及调用栈等核心概念,帮助理解 JS 代码“为何这样执行”。


一、JS 是如何被浏览器执行的?

JavaScript 是一门解释型脚本语言,在 Chrome 浏览器中由 V8 引擎负责编译与执行。虽然我们通常说 JS 是“边解释边执行”,但实际上 V8 采用了 即时编译(JIT) 技术,在代码执行前的一刹那完成编译。

JS 的执行过程分为两个关键阶段:

  • 编译阶段(Compilation Phase)
  • 执行阶段(Execution Phase)

这两个阶段交替进行:全局代码先编译再执行;遇到函数调用时,该函数体也会先编译再执行。


二、执行上下文与调用栈

1. 执行上下文(Execution Context)

执行上下文是引擎内部用于表示当前代码执行环境的抽象对象。每当一段可执行代码(如全局代码或函数)运行时,V8 会为其创建一个执行上下文对象,包含以下核心部分:

  • 变量环境(Variable Environment)
    存放使用 var 声明的变量、函数声明,以及函数参数。在编译阶段,这些标识符会被“提升”并初始化为 undefined(函数声明则直接赋值为函数体)。
  • 词法环境(Lexical Environment)
    存放使用 let/const 声明的变量。它们同样被提升,但处于 暂时性死区(Temporal Dead Zone, TDZ) ,在初始化前访问会报错。
  • this 绑定与作用域链信息

2. 调用栈(Call Stack)

JS 使用 调用栈 来管理执行上下文的生命周期:

  • 全局代码执行时,首先创建 全局执行上下文 并压入栈底。
  • 每当调用一个函数,就创建对应的 函数执行上下文 并压入栈顶。
  • 函数执行完毕后,其上下文出栈,相关变量可能被垃圾回收。

调用栈遵循 LIFO(后进先出) 原则,确保函数按正确顺序执行与返回。


三、编译阶段详解

以如下代码为例:

var a = 1;
function fn(a) {
    console.log(a);
    var a = 2;
    function a() {}
    var b = a;
    console.log(a);
}
fn(3);
console.log(a);

全局上下文编译阶段:

  • 创建全局执行上下文。
  • 变量环境:a = undefinedvar 提升)
  • 函数声明 fn 被提升并赋值为函数体。
  • 词法环境:空(无 let/const

执行 fn(3) 时:

  1. 创建新的函数执行上下文并压入调用栈。

  2. 编译阶段(函数内)

    • 形参 a 被放入变量环境,初始值为 3
    • 遇到 var a,由于 a 已存在(形参),不会重复创建,但后续赋值会覆盖。
    • 遇到 function a(){}函数声明优先级高于变量声明,因此 a 被覆盖为函数。
    • var b 被提升为 b = undefined
  3. 执行阶段

    • 第一次 console.log(a) 输出 ƒ a(){}(函数)。
    • 执行 var a = 2,将 a 赋值为 2
    • var b = ab = 2
    • 第二次 console.log(a) 输出 2

注意:函数声明提升 > 形参 > var 声明。


四、varlet/const 的本质区别

特性varlet / const
提升位置变量环境词法环境
初始化编译阶段设为 undefined编译阶段不初始化,处于 TDZ
重复声明允许(覆盖)不允许(语法错误)
作用域函数作用域块级作用域

示例:

console.log(a); // undefined(var 提升)
console.log(b); // ReferenceError: Cannot access 'b' before initialization
var a = 1;
let b = 1;

五、数据类型与内存模型简析

JS 数据分为两类:

  • 基本类型(值类型)string, number, boolean, null, undefined, symbol, bigint
    赋值时进行 值拷贝,互不影响。
  • 引用类型(对象)Object, Array, Function
    赋值时复制的是 内存地址(引用) ,修改会影响原对象。
let str = 'hello';
let str2 = str;        // 值拷贝
str2 = 'world';
console.log(str);      // 'hello'

let obj = { age: 18 };
let obj2 = obj;        // 引用拷贝
obj2.age++;
console.log(obj.age);  // 19

六、严格模式的影响

'use strict' 模式下:

  • 禁止隐式创建全局变量。
  • var 重复声明仍被允许(非语法错误),但某些行为更严格。
  • 更早暴露潜在错误,提升代码健壮性。

七、总结:JS 执行机制的核心要点

  1. 先编译,后执行
    JS 并非真正“边解释边执行”,而是在执行前瞬间完成编译(包括变量/函数提升)。

  2. 执行上下文是执行的基本单位
    全局代码和每个函数调用都会生成独立的执行上下文。

  3. 调用栈管理执行顺序
    栈结构保证了函数调用的嵌套与返回逻辑,执行完毕即销毁上下文。

  4. 变量提升的本质是“声明提前”

    • var 和函数声明进入 变量环境,初始化为 undefined 或函数体。
    • let/const 进入 词法环境,存在 TDZ,不可提前访问。
  5. 函数声明优先级最高
    在同一作用域中,函数声明会覆盖同名的 var 声明或形参。

  6. 作用域与内存模型影响变量行为
    理解值类型 vs 引用类型,有助于避免意外的副作用。


通过深入理解 V8 引擎的执行机制,我们不仅能解释“奇怪”的 JS 行为(如变量提升、函数覆盖),还能写出更可靠、可预测的代码。掌握执行上下文、调用栈与作用域链,是迈向高级 JS 开发的关键一步。