JavaScript 作用域与执行机制深度解析

作者:木棉时间:2026-10-26 01:40:13浏览:509

本文详解 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 进阶知识讲解系列第一讲。

四、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); // 200
var n = 100
function foo() {
  console.log(n); // undefined
  return
  var n = 200
 }
 foo()

第一个示例的原因众所周知,此处不再赘述。我们重点分析第二个示例为何打印出 undefined。如图所示,在编译阶段,函数会指向一个内存地址并开辟空间(AO)。因此,在代码执行时,引擎会在 AO 中查找变量,若未找到,则沿作用域链向上级查找。

JavaScript 进阶知识讲解系列第一讲。

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 作用域相关内容,请搜索本站历史文章或继续浏览后续系列。感谢大家的支持!

作者最新文章