news 2026/7/8 13:36:57

JavaScript 执行上下文与作用域链详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript 执行上下文与作用域链详解

一、作用域:起点

在讲执行上下文之前,我们先从作用域(Scope)入手。作用域简单说就是“变量起作用的范围”。

function bar() { var x = 10 console.log(x) // 10 } bar() console.log(x) // ReferenceError: x is not defined

x只在bar函数体内有效,这就是函数作用域。

ES6 之后还引入了块级作用域(let / const):

if (true) { let y = 20 } console.log(y) // ReferenceError

作用域规定了如何查找变量,也就是“在哪里找,怎么找”。

二、执行上下文 :代码运行的“环境”

执行上下文(简称 EC)是 JavaScript 引擎在执行代码时创建的一个抽象环境,它包含了当前代码运行所需的所有信息:变量、函数、this指向、作用域链等。

2.1 三种执行上下文

1. 全局执行上下文(Global EC)
- 最外层环境,在浏览器中就是window对象。
- 只存在一个,程序启动时创建。

2. 函数执行上下文(Function EC)
- 每当调用一个函数时,都会为该函数创建一个全新的执行上下文。
- 数量不限,函数调用结束则销毁(闭包除外)。

3. Eval 执行上下文(了解即可)

2.2 执行上下文栈(ECS):调用栈

JavaScript 是单线程的,同一时间只能做一件事。引擎用执行上下文栈(也叫调用栈)来管理所有的执行上下文。

function first() { console.log("first") second() } function second() { console.log("second") third() } function third() { console.log("third") } first()

执行过程如下:

1. 压入全局上下文(栈底)。
2. 调用 first() ,压入 first 上下文。
3. first 中调用 second(),压入 second。
4. second 中调用 third(),压入 third。
5. third 执行完毕,弹出;接着 second 弹出;最后 first 弹出。
6. 栈中只剩下全局上下文。

这就是我们常说的 “调用栈”,栈顶永远是当前正在执行的上下文。

三、执行上下文的生命周期:创建与执行

每个执行上下文都会经历两个阶段:

1. 创建阶段(进入上下文)
2. 执行阶段(代码执行)

在创建阶段,引擎会做三件重要的事:

- 创建 变量对象(Variable Object,VO)
- 建立 作用域链
- 确定 this 的指向

下面我们逐一拆解。

3.1 变量对象(VO)与活动对象(AO)

变量对象 是执行上下文中用于存储变量、函数声明和函数形参的容器。

- 在全局上下文中,变量对象就是全局对象本身(浏览器中为 window)。
- 在函数上下文中,变量对象被称为 活动对象(Activation Object,AO),因为它额外包含了 arguments 对象和形参。

创建阶段,VO/AO 会进行变量提升:

function test(a) { console.log(a) // 1 console.log(b) // undefined console.log(c) // undefined var b = 2 function c() {} } test(1) // 在创建阶段,AO 大致如下: AO = { arguments: { 0: 1, length: 1 }, a: 1, b: undefined, c: reference to function c() {} }

注意:函数声明会被完整提升(赋值),而变量声明只提升声明,赋值留在原地。

3.2 作用域链: 查找变量的“路线图”

作用域链 是一个由当前上下文及其所有父级上下文的变量对象按顺序组成的链表。当访问一个变量时,引擎会沿着这条链从顶端(当前 AO)开始查找,找不到则向上层查找,直到全局对象,如果还找不到则抛出 ReferenceError。

3.2.1 函数的 [[Scope]] 属性

每个函数在定义(声明)时,就会创建一个内部属性 [[Scope]],它保存了当前作用域链的快照。当函数被调用时,新生成的执行上下文的作用域链 = [[Scope]] + 自己的 AO。

这正是作用域链是静态(词法)作用域的原因 —— 它取决于函数定义的位置,而非调用位置。

var x = 10 function foo() { console.log(x) // 输出 10 还是 20 ? } function bar() { var x = 20 foo() } bar() // 10

因为 foo 定义在全局,其 [[Scope]] 中只有全局变量 x,即使被 bar 调用,也访问不到 bar 的局部变量。这就是词法作用域的精髓。

3.2.2 作用域链的完整构建过程

我们用一个综合性例子来走一遍:

var globalVar = 'global' function outer(outerParam) { var outerVar = 'outer' function inner(innerParam) { var innerVar = 'inner' console.log(globalVar, outerParam, outerVar, innerParam, innerVar) } inner('innerArg') } outer('outerArg')

步骤拆解:

1. 全局创建阶段:全局 VO = { globalVar: undefined, outer: function },作用域链 = [全局VO]。
2. 执行 outer('outerArg'):
- 创建 outer 的 AO:{ arguments: {0:'outerArg'}, outerParam: 'outerArg', outerVar: undefined, inner: function }
- outer 的作用域链 = [[Scope]](即全局VO) + 自己的 AO = [全局VO, outer AO]
- 执行代码,赋值 outerVar='outer',并定义 inner 函数。此时 inner 的 [[Scope]] = outer 当前的作用域链(即 [全局VO, outer AO])。
3. 执行 inner('innerArg'):
- 创建 inner 的 AO:{ arguments: {0:'innerArg'}, innerParam: 'innerArg', innerVar: undefined }
- inner 的作用域链 = [[Scope]](即 [全局VO, outer AO])+ 自己的 AO = [全局VO, outer AO, inner AO]
- 执行 console.log,沿链查找各变量,找到后输出。

运行结果:global outerArg outer innerArg inner

四、经典闭包:作用域链的“保鲜”

闭包的定义:函数 + 其词法环境的组合。更直白说,闭包是指一个函数能够记住并访问其定义时的作用域,即使该函数在其作用域之外执行。

闭包的产生,正是因为函数定义时的 [[Scope]] 被保留了下来,即使外层函数执行完毕并弹出栈,其 AO 依然被内部函数的 [[Scope]] 引用,不会被垃圾回收。

function createCounter() { let count = 0 return function() { count++ console.log(count) } } const counter = createCounter() counter() // 1 counter() // 2

分析:
- createCounter 执行后,返回匿名函数,该函数的 [[Scope]] 包含了 createCounter 的 AO(其中count=0)。
- createCounter 执行完毕,其 EC 弹出栈,但它的 AO 因被内部函数引用而不被销毁。
- 每次调用 counter,都会沿着作用域链找到那个 count,并修改它。

这就是闭包的本质 —— 作用域链的持久化引用。

五、进阶:块级作用域与 let / const 的“特殊”行为

ES6 引入了块级作用域,let 和 const 没有变量提升(实际上是有提升,但存在暂时性死区TDZ)。

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

在块级作用域的执行上下文中,let/const 声明的变量会绑定到块级作用域的“环境记录”中,但初始化为 uninitialized,进入 TDZ,直到赋值语句才变为可访问。这并不影响作用域链的结构,只是查找规则多了一个“是否已初始化”的判断。

六、分析

题目一:

var arr = [] for (var i = 0; i < 3; i++) { arr.push(function() { console.log(i) }) } arr[0]() // ? arr[1]() // ?/ arr[2]() // ?

答案:3 3 3(所有函数都打印 3)

解析:var i 是全局变量,循环结束后 i=3。三个函数共享同一个 i,执行时从全局 VO 中找到 i=3。

若想打印 0,1,2,可用闭包或 let:

- 闭包版:给每个函数绑定一个独立的作用域。
- let 版:for (let i=0; ...) 每次迭代会创建新的块级作用域,保存当时的 i。

题目二:

function Foo() { this.a = 1 return {} } var f = new Foo() console.log(f.a) // undefined

解析:构造函数如果显式返回一个对象,则 new 的结果为该对象,而非 this。这里 return {} 覆盖了默认的 this。

题目三(闭包与循环):

function test() { var x = 10 function inner() { console.log(x) } x = 20 return inner } var fn = test() fn() // 20

解析:inner 的 [[Scope]] 捕获的是 test 的 AO 的引用,x 在 test 执行阶段被修改为 20,所以输出 20(闭包保存的是引用,不是快照)。

七、总结

概念核心
执行上下文代码运行的环境,分为全局、函数、eval;由栈管理
变量对象/活动对象存储变量、函数声明、形参等;创建阶段完成提升
作用域链由当前 VO 及所有外层 VO 构成;查找变量时自上而下
词法作用域作用域由函数定义位置决定,与调用位置无关
闭包函数保留其定义时的作用域链引用,使得外层 AO 得以持久
this与作用域链无关,由调用方式决定(在此不展开)

理解执行上下文和作用域链,你就掌握了 JavaScript 的“内力”。后续无论学习异步、原型链还是框架源码,都会事半功倍。

核心:代码在编写时确定作用域,在运行时创建上下文,在栈中切换,在链中查找。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/8 13:33:50

MCP3428与PIC32MZ高精度数据采集方案详解

1. 为什么选择MCP3428PIC32MZ组合进行数据采集升级在工业测量和嵌入式系统中&#xff0c;数据采集的精度和实时性往往直接决定整个系统的性能天花板。传统方案常面临三个典型痛点&#xff1a;8-12位ADC分辨率不足导致微小信号丢失、MCU处理能力有限引发数据阻塞、多通道同步采样…

作者头像 李华
网站建设 2026/7/8 13:32:20

SSRF-King 与 Auto-SSRF 对比评测:2 款 Burp 插件在 5 类场景下的检测效果

SSRF-King 与 Auto-SSRF 深度评测&#xff1a;BurpSuite 插件在渗透测试中的实战表现在Web安全测试领域&#xff0c;服务器端请求伪造(SSRF)漏洞因其能够穿透网络边界、攻击内网服务而备受关注。作为渗透测试人员&#xff0c;我们经常需要高效准确地识别这类漏洞。本文将深入对…

作者头像 李华
网站建设 2026/7/8 13:32:19

PHP图片上传安全防御:从原理到实战,全面解析图片木马检测与防护

1. 项目概述与核心挑战在任何一个允许用户上传文件的Web应用中&#xff0c;图片上传功能几乎是标配。从社交媒体的头像更换&#xff0c;到电商平台的产品图上传&#xff0c;再到内容管理系统的文章配图&#xff0c;这个看似简单的功能背后&#xff0c;却隐藏着一个巨大的安全黑…

作者头像 李华
网站建设 2026/7/8 13:28:53

BurpSuite Intruder 单线程配置:规避验证码爆破中3类常见时序错误

BurpSuite Intruder 单线程配置&#xff1a;规避验证码爆破中的时序陷阱验证码爆破是安全测试中的常见场景&#xff0c;但高失败率往往让测试人员头疼不已。当使用BurpSuite Intruder配合验证码识别插件时&#xff0c;时序问题成为影响成功率的关键因素。本文将深入分析三类典型…

作者头像 李华
网站建设 2026/7/8 13:27:04

企业AI:初创成长公司必懂!

人工智能早已不再是科研实验室里的专属课题&#xff0c;也不再是硅谷巨头们的独有工具。面向大型组织搭建、可规模化落地的企业级人工智能&#xff0c;如今已经打破了规模壁垒&#xff0c;让初创企业与成长型中小企业也能轻松触达。接下来&#xff0c;我们将为你详解何为企业级…

作者头像 李华