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 = undefined(var提升) - 函数声明
fn被提升并赋值为函数体。 - 词法环境:空(无
let/const)
执行 fn(3) 时:
-
创建新的函数执行上下文并压入调用栈。
-
编译阶段(函数内) :
- 形参
a被放入变量环境,初始值为3。 - 遇到
var a,由于a已存在(形参),不会重复创建,但后续赋值会覆盖。 - 遇到
function a(){},函数声明优先级高于变量声明,因此a被覆盖为函数。 var b被提升为b = undefined。
- 形参
-
执行阶段:
- 第一次
console.log(a)输出ƒ a(){}(函数)。 - 执行
var a = 2,将a赋值为2。 var b = a→b = 2。- 第二次
console.log(a)输出2。
- 第一次
注意:函数声明提升 > 形参 >
var声明。
四、var 与 let/const 的本质区别
| 特性 | var | let / 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 执行机制的核心要点
-
先编译,后执行
JS 并非真正“边解释边执行”,而是在执行前瞬间完成编译(包括变量/函数提升)。 -
执行上下文是执行的基本单位
全局代码和每个函数调用都会生成独立的执行上下文。 -
调用栈管理执行顺序
栈结构保证了函数调用的嵌套与返回逻辑,执行完毕即销毁上下文。 -
变量提升的本质是“声明提前”
var和函数声明进入 变量环境,初始化为undefined或函数体。let/const进入 词法环境,存在 TDZ,不可提前访问。
-
函数声明优先级最高
在同一作用域中,函数声明会覆盖同名的var声明或形参。 -
作用域与内存模型影响变量行为
理解值类型 vs 引用类型,有助于避免意外的副作用。
通过深入理解 V8 引擎的执行机制,我们不仅能解释“奇怪”的 JS 行为(如变量提升、函数覆盖),还能写出更可靠、可预测的代码。掌握执行上下文、调用栈与作用域链,是迈向高级 JS 开发的关键一步。