☰
现代 JavaScript 教程练习精讲:用闭包实现双括号调用 `sum(a)(b)`
2026/10/7 2:21:35 网站建设 项目流程
  • 文档
  • 教程
  • 前端

【免费下载链接】zh.javascript.info

现代 JavaScript 教程(The Modern JavaScript Tutorial),以最新的 ECMAScript 规范为基准,通过简单但足够详细的内容,为你讲解从基础到高阶的 JavaScript 相关知识。

项目地址:https://gitcode.com/gh_mirrors/zh/zh.javascript.info
点击查看免费下载

本篇指南基于《现代 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

逐步拆解执行过程:

  1. sum(1)被调用,参数a被赋值为1;
  2. sum内部返回了一个新的匿名函数function(b) { return a + b; };
  3. 紧接着的(2)就是对这个返回函数的调用,参数b被赋值为2;
  4. 匿名函数体中的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

自测要点:

  1. sum(1)的返回值类型必须是function(可用typeof sum(1) === "function"验证);
  2. 返回值函数仍能访问参数a,证明闭包捕获生效;
  3. 双括号调用与普通两次调用等价: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 相关知识。

项目地址:https://gitcode.com/gh_mirrors/zh/zh.javascript.info
点击查看免费下载
上一篇:网盘下载太慢怎么办?8大平台直链解析工具LinkSwift深度评测
下一篇:LinkSwift:9大网盘直链下载助手终极指南,告别限速烦恼

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询