news 2026/8/22 3:38:11

闭包里的变量到底存哪了?图解 JS 词法环境与内存引用链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
闭包里的变量到底存哪了?图解 JS 词法环境与内存引用链路

拒绝玄学,看透本质:图解 JavaScript 词法环境与内存引用

很多同学在学习闭包时,往往只记住了“子函数可以使用父函数的变量”这个结论,却对底层的**“词法环境(Lexical Environment)”**知之甚少。

本文将剥离所有晦涩的术语堆砌,通过一段经典代码,深入内存底层,向你展示**代码逻辑(Code)、变量数据(Data)和执行环境(Environment)**在运行时的真实物理关系。

案例代码

我们以这段经典的闭包代码为例:

JavaScript

// 全局环境 let a = 100 function fun() { let timer = 0 // 局部变量 // 定义内部函数 function test() { timer++ console.log(timer) } return test } // 核心时刻:函数执行,并返回内部函数 const myTest = fun()

第一部分:直觉的误区(为什么你会困惑?)

初学者最容易犯的错误,是认为内存结构等同于代码结构。

错误的静态视角:

“因为代码里 test 嵌套在 fun 里,所以内存里 test 的数据也死死地包在 fun 里。fun 执行完,这块空间就该销毁,或者一直死板地存在那里。”

这种“俄罗斯套娃”式的直觉(如下图),无法解释为什么fun执行完了,timer还能被访问,也无法解释什么是“动态的作用域链”。

Code snippet

全局内存
let a = 100
fun 的内部空间 (静态嵌套)
let timer = 0
function test 代码

第二部分:揭秘真相——什么是“词法环境”?

要理解闭包,必须先理解 JavaScript 引擎执行代码时的分离存储原则。

在 JavaScript 运行时,“代码逻辑”和“状态数据”是分开存放的,靠“引用(指针)”连接。而管理这些状态数据的核心结构,就叫词法环境(Lexical Environment)

1. 核心概念定义

一个标准的词法环境由两部分组成:

  1. 环境记录器(Environment Record):这就是一个“登记表”,专门用来存放变量和函数的声明。比如timer = 0就记在这里。

  2. 外部环境引用(Outer Reference):这是一个“指针”,指向父级的词法环境。这就是作用域链的物理实体。

2. 运行时拆解:变量、函数、代码究竟在哪?

当执行const myTest = fun()这行代码时,内存中发生了极其精密的动态构建过程。

请配合下方的高维内存模型图来阅读:

  • 变量存在哪?(数据)

    当 fun() 被调用时,引擎在内存中创建了一个全新的词法环境(Lexical Environment)。变量 timer 的值 0 被保存在这个环境的环境记录器中。

  • 代码存在哪?(逻辑)

    函数 test 的代码逻辑(即函数体内的字符串)并不存储在词法环境里,而是存储在堆内存(Heap) 中一个独立的函数对象(Function Object)里。

  • 它们如何相互引用?(关键!)

    这是最关键的一步。当 test 函数对象被创建时,引擎会给它装上一个不可见的内部属性 [[Environment]]。这个属性直接指向了 fun 执行时创建的那个词法环境。


第三部分:一张图看懂内存物理结构

下面的 Mermaid 图解展示了fun()执行瞬间的真实内存快照。请重点关注代码存储(Heap)与环境记录(Stack/Context)的分离与连接。

Code snippet

堆内存 (存储函数实体/代码)
执行环境栈 (存储变量状态)
全局词法环境 (Global LE)
fun 函数词法环境 (Fun LE)
myTest 引用
闭包核心:
[[Environment]] 指针强引用
ƒ fun() 对象实体
Code: ...
ƒ test() 对象实体
Code: timer++ ...
-----------------------
[[Environment]]内部插槽
环境记录器:
timer: 0
Outer: -> Global LE
环境记录:
a: 100
fun: pointer -> func_fun
myTest: pointer -> func_test
Outer: null

第四部分:总结与顿悟

通过上图,我们可以回答最初的困惑:

  1. 为什么 fun 执行完,timer 不消失?

    看图中的红色粗线。虽然 fun 函数执行结束了,但返回的 test 函数对象里有一个 [[Environment]] 指针,死死地拉住了 fun 的词法环境。只要 myTest 还在引用 test 对象,这个Fun LE方块就永远无法被垃圾回收。

  2. 代码和环境是如何分离的?

    test 的代码逻辑静静地躺在堆内存里(右边),而它所需要操作的数据 timer 躺在动态生成的词法环境里(左边)。两者通过指针跨越内存区域相连。

所谓闭包,本质上就是:一个函数对象(代码)保留了对它出生地(词法环境)的引用。

理解了这一点,你就理解了 JavaScript 内存模型的核心。

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

36、深入探索Bash配置与自定义

深入探索Bash配置与自定义 1. ANSI颜色设置 在ANSI标准中,颜色和属性通过特定的代码来表示。例如, 1;34m 表示“将字符属性设置为亮色,字符颜色设置为蓝色”, 0m 表示“清除所有属性并取消颜色设置”。以下是一个展示所有可能颜色组合的脚本: #!/usr/bin/env bash…

作者头像 李华
网站建设 2026/8/21 5:07:05

38、优化bash使用:深入底层、添加功能与改进补全

优化bash使用:深入底层、添加功能与改进补全 在日常使用bash shell时,我们常常会遇到各种操作上的不便,例如在深目录结构中导航、添加自定义功能以及让命令补全更智能等。本文将针对这些问题,为你提供实用的解决方案。 深入目录底层 在处理狭窄但深度很深的目录结构时,…

作者头像 李华
网站建设 2026/8/21 20:17:14

豆包2025深度体验:从代码生成到文档导出,国产AI助手的效率革命

作为常年与代码、文档打交道的技术开发者,我试过市面上十余款AI助手,却在2025年被字节跳动的豆包彻底圈粉。这款月活突破1.59亿的国产AI黑马,不仅在代码生成、技术文档撰写上表现惊艳,更以"边想边搜原生导出"的核心优势…

作者头像 李华
网站建设 2026/8/21 15:37:31

15、FrameMaker图形与色彩使用指南

FrameMaker图形与色彩使用指南 1. 自定义对象属性 不同类型的图形在FrameMaker中有特定的自定义对象属性,具体如下表所示: | 对象类型 | 附加信息 | | ---- | ---- | | 弧形 | 起始角度和结束角度 | | 文本列 | 流标签和自动连接设置(通常在流属性对话框中指定) | |…

作者头像 李华
网站建设 2026/8/20 22:30:10

28、文档排版与引用实用技巧分享

文档排版与引用实用技巧分享 在文档处理过程中,我们常常会遇到各种排版和引用方面的难题。下面为大家详细介绍一些实用的技巧,帮助大家更高效地完成文档编辑工作。 侧边栏表格排版 在文档中设置侧边栏表格时,其与前后段落的间距调整是个难点。通常表格会出现在插入位置的…

作者头像 李华
网站建设 2026/8/21 21:55:55

Node.js多线程调试终极指南:用ndb轻松驾驭Worker_threads

Node.js多线程调试终极指南:用ndb轻松驾驭Worker_threads 【免费下载链接】ndb ndb is an improved debugging experience for Node.js, enabled by Chrome DevTools 项目地址: https://gitcode.com/gh_mirrors/nd/ndb 还在为Node.js多线程调试而头疼吗&…

作者头像 李华