作用域链 × 闭包:三段代码,看懂 JavaScript 的套娃人生

作者:烟袅日期:2025/11/28

一句话结论:
JS 的变量查找永远沿「作用域链」往上爬;
闭包则把“本该销毁”的作用域偷偷塞进函数口袋,让它长生不死。

下面用三段递进式代码,从“最简 demo”到“闭包 full feature”,逐行剖给你看。


第一段:最简作用域链——「静态词法」碾压「动态调用」

1var myName = '极客世界';
2
3function bar() {
4  console.log(myName);   //  打印什么?
5}
6
7function foo() {
8  var myName = '极客邦';
9  bar();                 //   foo 里调用 bar
10}
11
12foo();
13

运行结果

1极客世界
2

为什么不是“极客帮”?

在我们的脑海里看到这幅代码应该很快就能想到下面这幅图

对啊如果是这幅图按调用栈的顺序没找到应该往下找啊,但是这其实是一个坑。 先来讲讲作用域链吧。 作用域链也叫词法作用域链,静态的,只和函数声明的位置有关,在编译阶段就决定好了,和调用没有关系。 真正的查找应该是按作用域链的规则查找,也就是按下面这幅图的规则

  • 作用域链在定义时(词法阶段)就写好,跟在哪调用没半毛钱关系;
  • bar 的外部词法环境是 全局
  • 变量查找路径:bar → 全局;永远找不到 foomyName

记住口诀:“调用位置只影响栈,变量查找只看出生证明。”


第二段:套娃加深——“同名变量”在作用域链上打伏击

1let myAge = 10;
2let test = 1;               // 全局 test
3var myName = '极客时间';     // 全局 myName
4
5function bar() {
6  var myName = '极客世界';   //  bar 函数环境
7  let test1 = 100;
8  if (1) {
9    let myName = 'Chrome 浏览器'; //  块级环境
10    console.log(test);     // 打印哪个 test 
11  }
12}
13
14function foo() {
15  var myName = '极客邦';    //  foo 函数环境
16  let test = 2;             // 遮蔽全局 test
17  {
18    let test = 3;           // 块级 test,完全互不影响
19    bar();                  // 在块里调用 bar
20  }
21}
22
23foo();
24

运行结果

11
2

作用域链图解

  • 块级 test=3test=2 都住在更外层,对 bar 不可见
  • 再次验证:词法作用域只看出身,不看调用栈。

第三段:闭包登场——把外层环境“偷渡”出栈

1function foo() {
2  var myName = '极客时间';
3  let test1 = 1;
4  let test2 = 2;
5
6  // 1. 对象里两个函数都「引用」外部变量
7  var innerBar = {
8    getName: function () {
9      console.log(test1); // 引用 test1
10      return myName;      // 引用 myName
11    },
12    setName: function (newName) {
13      myName = newName;   // 修改外部 myName
14    }
15  };
16  return innerBar;        // 2. 把内部对象抛到外部
17}
18
19var bar = foo();          // foo 执行上下文出栈
20                          //  myName/test1/test2 因为被引用**不会**被 GC
21
22bar.setName('极客邦');    // 修改的是原来 foo 环境下的 myName
23console.log(bar.getName()); // 1. 先打印 test1  1
24                            // 2. 再打印返回值  极客邦
25

闭包形成条件(背)

  1. 函数嵌套函数;
  2. 内部函数引用外部变量;
  3. 内部函数被返回到外部并存活。

V8 底层怎么做到的?

按理说在var bar=foo() ;这一步的结束的时候要执行出栈操作 bar 里面的变量要回收吧? 但是实际情况并没有这是为什么呢?其实是因为**foo 函数执行完后,其执行上下文从栈顶弹出了,但是由于返回的setName,getName 使用了foo 函数内部的变量myName和tsst1,这两个变量依然在内存中,有点像getName,setName 方法背的一个专属背包。**这个背包我们就叫做闭包,这个闭包里面的变量叫自由变量 看图理解吧

因为你在 foo() 内部定义了 getNamesetName 这两个函数,并且它们引用了 foo 内部的变量(比如 myNametest1),那么:

  • 这两个函数就形成了闭包
  • 它们会“记住”自己被创建时所处的词法环境(也就是 foo 的作用域)。
  • 即使 foo() 执行完毕、调用栈弹出,只要这两个函数还被外部(比如 bar)引用着,JavaScript 引擎就不会销毁 foo 中被引用的那些变量。

你可以形象地理解为:
每个函数都背着一个小背包(闭包),里面装着它创建时能访问到的外部变量。

  • 于是 test1 还能读到 1,myName 还能被改。

口诀:“栈帧可死,闭包长存。”


日常开发 3 句忠告

  1. 少用闭包随便拉数据——内存泄漏就是这么来的;
  2. 循环里返回函数先 let 再包,别用 var
  3. 调试打开 DevTools → Scope 面板,Local/Closure/Global 一眼看清变量到底从哪来。

30 秒背完收工

变量查找看出生,调用位置是浮云;
内部函数拎外部,闭包环境永长存。


作用域链 × 闭包:三段代码,看懂 JavaScript 的套娃人生》 是转载文章,点击查看原文


相关推荐


Next.js第十一章(渲染基础概念)
小满zs2025/11/25

渲染基础 本章我们学习 CSR SSR SSG 三种渲染方式,以及Hydration水合的概念。 CSR CSR是Client Side Rendering的缩写,即客户端渲染。像我们使用的Vue React Angular 等框架,都是CSR。 工作流程如下: 浏览器请求服务器 -> 服务器返回HTML/JS/CSS等文件 -> JS动态渲染生成DOM -> 浏览器渲染DOM graph TD A[浏览器发起请求] --> B[服务器返回<br/>空 HTML 骨架 + JS/CSS] B -


docker中安装php运行环境
AdaTina2025/11/23

1. 拉取镜像 docker pull php:7.4-fpm # 以 PHP 7.4 为例 docker pull nginx:latest docker pull mysql:8.0 2. 创建网络 docker network create -d bridge my-network #   -d:让容器在后台以守护进程模式运行 3. 运行 MySQL 容器 docker run -d --name mysql8.0 --network my-network


CANN 异构计算架构:释放昇腾 AI 算力潜能,赋能自动驾驶实时感知
LucianaiB2025/11/22

CANN 异构计算架构:释放昇腾 AI 算力潜能,赋能自动驾驶实时感知 2025年10月,某自动驾驶算法团队在昇腾 Atlas 900 服务器上完成了一项关键测试:基于 CANN 架构优化的 FlashAttention 算子,将激光雷达点云实时处理延迟从 80ms 降至 15ms,这意味着原本需要 4 张 GPU 卡才能运行的感知算法,现在只需单张昇腾 910B 即可实现!这个突破背后,正是异构计算架构 CANN 释放的硬件潜能。 CANN异构计算架构:从硬件到应用的多层次协同设计 CANN(


WPF命令
她说彩礼65万2025/11/20

命令是一种设计模式(命令模式,Command Pattern),用于将“请求”封装为一个对象,从而: 解耦调用者(如按钮)与执行者(如 ViewModel 中的方法) 支持统一的启用/禁用控制(CanExecute) 实现撤销(Undo)、日志、队列等高级功能 在 WPF 中,命令通过 ICommand 接口实现。 public interface ICommand { event EventHandler CanExecuteChanged; bool CanExecute(o


私有化部署的gitlab的push failed问题,使用http远程连接(使用token或用户、密码)
知兀2025/11/19

报错 我使用了ssh push代码,结果push失败 ping ip也可能ping通 使用http远程连接仓库 一问才知道,服务器没开ssh 用http的连接 git remote add origin http://<局域网ip>/xxx.git Token 连接成功后,我想要push代码,然后出现了 右侧的 “Generate...” 按钮用于引导你在 GitLab 中生成符合 IDEA 集成要求的个人访问令牌 生成后复制 通过用户、密码登录


Python 的内置函数 tuple
IMPYLH2025/11/17

Python 内建函数列表 > Python 的内置函数 tuple Python 的内置函数 tuple() 用于创建一个不可变的序列(元组)。以下是关于 tuple() 函数的详细说明: 功能描述 tuple() 函数可以将可迭代对象(如列表、字符串、集合等)转换为元组。如果调用时不传入参数,则返回一个空元组。 语法 tuple(iterable) iterable(可选):任何可迭代对象(如列表、字符串、字典等)。如果未提供,则返回空元组 ()。 返回值 返回一个包含输入


Python 的内置函数 range
IMPYLH2025/11/16

Python 内建函数列表 > Python 的内置函数 range Python的内置函数range详解 range()是Python中一个非常实用的内置函数,主要用于生成一个不可变的整数序列。它在循环和迭代操作中应用广泛。 基本语法 range()函数有三种调用方式: range(stop):生成从0开始到stop-1的整数序列range(start, stop):生成从start开始到stop-1的整数序列range(start, stop, step):生成从start开始到s


在 Linux 环境中配置 Eclipse 以开发 Hadoop 应用
EmoGP2025/11/15

手动导入 JAR 包(无 Maven 时) 右键 Eclipse 项目 → Properties → Java Build Path → Libraries → Add External JARs。 依次选择以下目录中的所有 JAR 包


VisionWeaver:从“现象识别”到“病因诊断”,开启AI视觉幻觉研究新篇章
哔哩哔哩技术2025/11/14

前言 长久以来,我们只知道大型视觉语言模型(LVLM)会犯错,但始终缺乏一把“手术刀”,无法剖析其视觉感知的根源性缺陷。我们只知其然,不知其所以然。我们希望当 AI 模型观察图像时,不再凭空想象,不再“指鹿为马”。 现在,这一瓶颈被打破了。bilibili 用户技术中心提出 VisionWeaver 及其核心诊断工具 VHBench-10,带来了创新性的视角。VisionWeaver 不再依赖单一编码器,而是开创性地提出“上下文感知路由网络”,动态协同多个“视觉专家” 。而这一切得以实现的基础,


副业搞个 100 万,这 5 个 GitHub 项目不能错过。
逛逛GitHub2025/11/13

01、一人企业方法论 开源项目名字叫做《一人企业方法论》,目前已经更新到第二版。专门为想要独立创业或开展副业的个人提供实用指导。 无论是做自媒体、电商还是数字商品,即使没有技术背景,也能从中获得启发。 我读下来,这个开源项目的核心思想和我的想法非常契合。它倡导一种以个人为核心、精益化、可持续的商业模式。  它不追求规模化增长和资本驱动,而是专注于利用现代技术工具和系统化方法论,让个人能够独立构建并运营一个健康、盈利的微型企业。 如果你厌倦了大厂的撕逼,无效了的内卷,想找寻一份靠谱的副业。建议

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2025 聚合阅读