JavaScript 作用域与执行机制深度解析
本文详解 JavaScript 核心概念,涵盖浏览器内核与 V8 引擎的工作机制。深入剖析全局对象、执行上下文栈及变量提升原理,通过代码示例解析 undefined 输出原因与作用域链查找规则,帮助开发者掌握 JS 执行流程与内存管理基础。
JavaScript 进阶知识讲解系列第一讲。
本文将系统梳理 JavaScript 中容易混淆的核心概念,包括作用域、函数、闭包、面向对象编程、ES 新特性、事件循环机制、微任务与宏任务调度、内存管理、Promise 异步处理、async/await 语法以及防抖与节流等性能优化技巧。
一、浏览器内核概述
1. 常见浏览器内核类型
Gecko:早期由 Netscape 和 Mozilla Firefox 浏览器采用。Trident:由微软开发,曾用于 IE4 至 IE11 浏览器。Webkit:苹果基于 KHTML 开发的开源内核,用于 Safari 浏览器,Google Chrome 早期版本也曾使用。Blink:作为 Webkit 的分支,由 Google 开发,目前应用于 Google Chrome、Edge 及 Opera 等主流浏览器。
二、JavaScript 引擎基础
2.1 为什么需要 JavaScript 引擎?
高级编程语言最终都需要转换为机器指令才能执行。无论代码由浏览器还是 Node.js 环境运行,最终都必须由 CPU 执行。由于 CPU 仅能识别其特定的指令集(即机器语言),因此需要 JavaScript 引擎将 JavaScript 代码翻译为 CPU 可执行的指令。
2.2 常见的 JavaScript 引擎
常见的引擎包括 SpiderMonkey、Chakra、JavaScriptCore 和 V8 等。目前应用最广泛的是 V8 引擎。
三、V8 引擎详解
3.1 官方定义
- V8 是一个由 C++ 编写的 Google 开源高性能 JavaScript 和 WebAssembly 引擎,广泛应用于 Chrome 浏览器和 Node.js 环境。
- 它实现了 ECMAScript 和 WebAssembly 标准,支持在 Windows 7 及以上版本、macOS 10.12+ 以及使用 x64、IA-32、ARM 或 MIPS 处理器的 Linux 系统上运行。
- V8 既可以独立运行,也可以嵌入到任何 C++ 应用程序中。
3.2 解析过程图示

四、JavaScript 的执行过程
- 初始化全局对象(GO - Global Object):JavaScript 引擎在执行代码前,会在堆内存中创建一个全局对象,并将 window 属性指向该对象。同时,Date、Array、String、Number、setTimeout 等内置对象以及用户定义的全局变量都会被添加到 GO 中(此时用户定义的变量尚未执行,值为 undefined)。这也是为什么可以直接使用 window. 或 Date 等函数或类的原因。
- 执行上下文栈(ECS - Execution Context Stack):这是用于执行代码的调用栈,主要执行全局代码块(GEC - Global Execution Context)。GEC 会被压入 ECS 中进行执行。
- GEC(内部包含 VO,指向 GO)被压入 ECS 中。
- GEC 开始执行代码,按照从上到下的顺序依次执行。
4.1 普通代码执行分析
在 GEC 开始执行代码后,如果仅涉及变量声明,逻辑相对直观。例如:
console.log(a); // undefined
var a = 100此处不报错而输出 undefined,原因在于前文所述:在创建 GO 对象时,定义的全局变量已被添加到 GO 中,但初始值为 undefined。这体现了 var 声明的变量提升特性。
4.1 函数如何执行?
如果在执行过程中遇到函数调用,会发生什么?
foo()
function foo() {
console.log(100);
}
// foo()观察上述函数,无论在哪里调用 foo(),它都能正确执行,而不会像变量那样显示 undefined 或报错。原因在于:在 GO 创建阶段(即编译时,代码尚未开始执行),当引擎遇到函数定义时,会根据函数体创建一个函数执行上下文(FEC,其中包含 VO,指向 AO)并将其压入 ECS 中。此时存储的是内存地址,而非 undefined。因此,当代码执行到 foo() 时,引擎能在 GO 中找到该内存地址并执行。
五、深入理解作用域提升与 undefined
var n = 100
function foo() {
n = 200
}
foo()
console.log(n); // 200var n = 100
function foo() {
console.log(n); // undefined
return
var n = 200
}
foo()第一个示例的原因众所周知,此处不再赘述。我们重点分析第二个示例为何打印出 undefined。如图所示,在编译阶段,函数会指向一个内存地址并开辟空间(AO)。因此,在代码执行时,引擎会在 AO 中查找变量,若未找到,则沿作用域链向上级查找。

function foo() {
console.log(a);// undefined
var a = 100
console.log(a);// 100
}
var a = 100
foo()function foo() {
console.log(a);// 100
}
var a = 100
foo()再看这两个示例,相信读者已明白第一个输出的原因。我们重点分析第二个示例为何输出 100 而非 undefined。原因很简单,这涉及作用域链:显然 AO 中没有变量 a 的定义,因此引擎会在上一层级查找。这里的上一层级即 GO,此时 GO 中的 a 值已为 100,因此查找到的 a 即为 100。
请大家思考下面这段代码的输出结果是什么?
var a = 1
function foo1() {
console.log(a);
}
function foo2() {
var a = 2
console.log(a);
foo1()
}
foo2()
console.log(a);至此,关于 JavaScript 进阶讲解之一:作用域的文章介绍完毕。如需了解更多 JS 作用域相关内容,请搜索本站历史文章或继续浏览后续系列。感谢大家的支持!
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
D3.js 与 ECharts 选型对比:灵活性 vs 开发效率
2026-11-05 09:45:01
为什么JS中typeof []返回object:数组类型检测误区解析
2026-11-04 19:50:47
D3.js 新手入门:环境搭建与第一个条形图实现
2026-11-04 10:49:48
