- 文档
- 教程
- 前端
【免费下载链接】zh.javascript.info
现代 JavaScript 教程(The Modern JavaScript Tutorial),以最新的 ECMAScript 规范为基准,通过简单但足够详细的内容,为你讲解从基础到高阶的 JavaScript 相关知识。
本篇指南基于《现代 JavaScript 教程》(The Modern JavaScript Tutorial)中文仓库中的"闭包 sum"练习(任务题目 与 官方解答),系统讲解如何编写一个支持sum(a)(b) = a+b双括号调用的sum函数。文章将结合变量作用域与闭包主教程中的词法环境(Lexical Environment)与[[Environment]]原理,剖析这类"返回函数"写法背后的闭包机制,读完你不仅能独立完成该练习,还能举一反三地理解柯里化风格调用、计数器(makeCounter)等常见闭包模式。
练习目标:让函数支持两次调用
题目要求非常简短:编写一个像sum(a)(b) = a+b这样工作的sum函数——是的,就是这种通过双括号的方式(并不是错误)。即调用一次sum(a)之后,紧接着再调用一次返回结果,两次括号分别接收一个参数,最终完成两个参数的加法。
预期的行为如下:
sum(1)(2) = 3 sum(5)(-1) = 4该练习在原仓库中被标记为importance: 4(重要度较高),位于闭包章节的练习题序列中,是检验读者是否真正理解"函数可以返回函数、内部函数可以访问外部变量"这一核心知识的经典入门题。
核心思路:第一个括号必须返回一个函数
要使sum(a)(b)这种写法成立,关键推理只有一步:sum(a)的结果必须是一个函数,这样紧接着的(b)才能被当作一次函数调用。JavaScript 中的函数是一等公民,可以像普通值一样被返回,因此我们完全可以写出"返回函数的函数"。
按照官方解答的思路,为了让第二个括号有效,第一个括号必须返回一个函数:
function sum(a) { return function(b) { return a + b; // 从外部词法环境获得 "a" }; } alert( sum(1)(2) ); // 3 alert( sum(5)(-1) ); // 4逐步拆解执行过程:
sum(1)被调用,参数a被赋值为1;sum内部返回了一个新的匿名函数function(b) { return a + b; };- 紧接着的
(2)就是对这个返回函数的调用,参数b被赋值为2; - 匿名函数体中的
a并不是它自己的局部变量,而是来自外层sum调用的词法环境,通过闭包机制仍然可访问,于是计算出1 + 2 = 3。
同理,sum(5)(-1)中a = 5、b = -1,最终得到4。
原理深挖:为什么内部函数还能访问a
初看之下,读者最容易困惑的点是:sum调用结束后,参数a不是应该"消失"了吗?为什么返回的内部函数还能读到它?答案在于 JavaScript 的词法环境(Lexical Environment)与函数隐藏属性[[Environment]]。
根据闭包主教程的讲解:
- 每次函数调用开始时,都会自动创建一个新的词法环境对象,用于存储该次调用的局部变量和参数;
- 每个函数在"诞生"时都会通过隐藏属性
[[Environment]]记住创建它的词法环境的引用,且该引用在创建时被设置并永久保存; - 当代码访问一个变量时,会先搜索内部词法环境,找不到再顺着对外部词法环境的引用逐层向外查找,直到全局词法环境。
回到本题:sum(1)的调用创建了一个词法环境,其中记录了a = 1。随后在该环境内部创建的匿名函数,其[[Environment]]就指向这个环境。当(2)调用匿名函数时,函数体内查找b会在自己的词法环境中命中(值为2),查找a时内部没有,于是顺着外部引用找到sum(1)的词法环境,命中a = 1。这就是"从外部词法环境获得a"的完整链路。
从闭包的定义来看:闭包是指一个函数可以记住其外部变量并可以访问这些变量。在 JavaScript 中,所有函数都是天生闭包的——函数会自动通过隐藏的[[Environment]]属性记住创建它们的位置,因此它们都能访问外部变量。本题正是这一特性的最小化演示。
与makeCounter的对比
仓库中同章节的Counter 独立性问题使用了几乎相同的机制:
function makeCounter() { let count = 0; return function() { return count++; }; }makeCounter返回的函数同样通过闭包捕获了外层count变量;每次调用makeCounter()都会创建一个全新的词法环境,因此counter与counter2各自持有独立的count,互不干扰(输出为0,1,0,1而非0,1,2,3)。与本题对比可以看出同一种模式的两种形态:
sum(a)的闭包捕获的是函数参数a(只读使用);makeCounter()的闭包捕获的是局部变量count(跨多次调用持续读写)。
两者都印证了主教程中的结论:"在变量所在的词法环境中更新/读取变量",以及"不论在哪里调用,函数仍然可以访问创建它时的外部变量"。
运行与验证
原仓库的解答使用了浏览器环境下的alert输出结果,你可以直接在浏览器控制台或任意支持 JavaScript 的运行环境中执行:
function sum(a) { return function(b) { return a + b; }; } console.log(sum(1)(2)); // 3 console.log(sum(5)(-1)); // 4自测要点:
sum(1)的返回值类型必须是function(可用typeof sum(1) === "function"验证);- 返回值函数仍能访问参数
a,证明闭包捕获生效; - 双括号调用与普通两次调用等价:
sum(a)(b)恒等于先let f = sum(a); f(b)。
常见的错误写法
- 直接相加后返回数值:
return a + b——这样sum(1)得到3的数值,无法再被(2)调用,会抛出TypeError: ... is not a function; - 用箭头函数同样可行:
const sum = a => b => a + b;,原理完全相同,只是语法更简洁。
延伸思考:通向柯里化
"返回函数的函数"这一模式是函数式编程中**柯里化(Currying)**与部分应用的基础形态。sum(a)(b)其实就是把"接收两个参数的函数"改写为"接收第一个参数并返回接收第二个参数的函数"。
仓库同章节的使用闭包求和 sum(a)(b)(c)...姊妹题以及makeArmy 练习都建立在同一套词法环境机制之上:只要理解了"每次调用创建独立词法环境 + 返回函数通过[[Environment]]捕获环境"这两条规则,就能正确预测闭包在各种嵌套场景下的行为。
小结
sum(a)(b)能工作的根本原因是sum(a)返回了一个函数,函数是一等公民,可以被返回并立即再次调用;- 返回的内部函数通过闭包访问外层参数
a,其机制是词法环境链与隐藏属性[[Environment]]; - 该练习是理解 JavaScript 闭包、柯里化风格的敲门砖,同章节的
makeCounter、sum系列练习可一并对照学习。
如需继续深入,可研读闭包主教程中关于词法环境、垃圾回收与 V8 引擎对未使用变量的优化(调试时外部变量可能显示 "No such variable")等进阶内容。
- 文档
- 教程
- 前端
【免费下载链接】zh.javascript.info
现代 JavaScript 教程(The Modern JavaScript Tutorial),以最新的 ECMAScript 规范为基准,通过简单但足够详细的内容,为你讲解从基础到高阶的 JavaScript 相关知识。
相关推荐
用闭包实现双括号求和:JavaScript 教程 sum(a)(b) 任务全解
用闭包实现双括号求和:JavaScript 教程 sum a b 任务全解 导读 sum a b = a+b 是 en.javascript.info(Mode
文档/教程前端JavaScript 闭包实战:用双括号 `sum(a)(b)` 实现函数求和
JavaScript 闭包实战:用双括号 sum a b 实现函数求和 导读 sum a b = a + b 是 JavaScript 教程中检验"闭包 + 词
文档/教程前端algorithm-pattern链表模板:哑节点+快慢指针,一套模板秒杀6类链表高频面试题
algorithm pattern链表模板:哑节点+快慢指针,一套模板秒杀6类链表高频面试题 面试准备中最头疼的就是 链表题 :去重、反转、合并、找中点、判断成
文档教程前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考