深入理解JavaScript闭包:从基础概念到实际应用

作者:函数茶馆

时间:2026-10-26

浏览:488

本文深入解析JavaScript闭包的核心概念,通过嵌套函数、返回值及对象引用传递等实例,阐明内部函数如何访问外部函数变量。文章还探讨了闭包在数据隐藏、定时器应用及潜在内存泄漏问题中的表现,帮助开发者掌握这一关键特性。

文章目录

JavaScript闭包基础概念

国内技术社区往往缺乏深入的教学氛围。为了彻底搞懂JavaScript中的闭包概念,我查阅了大量英文资料。直到在Stack Overflow上看到一篇高质量解答,我才确信这就是闭包的本质。



以下是该解答的中文翻译,仅供参考。



提问者 Peter Mortensen 提出:



正如爱因斯坦所言:“如果你无法向六岁的孩子解释清楚,说明你自己也没真正理解。”我尝试向一位27岁的朋友解释JS闭包(JavaScript closure),但彻底失败了。



你会如何向一个充满好奇心的六岁孩子解释这个概念?



注意:我看过Stack Overflow上的其他示例,但那些解释对我没有帮助。



用户 Ali 的回答:



当函数内部嵌套另一个函数时,内部函数可以访问外部函数的变量。


示例代码如下:

 
function foo(x) {
var tmp = 3;
function bar(y) {
alert(x + y + (++tmp));
} 
bar(10);
} 
foo(2)

无论执行多少次,结果都会弹出 16。这是因为 bar 函数可以访问 foo 的参数 x 以及变量 tmp。



但这还不是闭包。只有当返回的是内部函数时,才构成闭包。内部函数会“封闭”外部函数的变量,直到内部函数执行完毕。


示例代码如下:

 
function foo(x) {
var tmp = 3;
return function (y) {
alert(x + y + (++tmp));
} 
} 
var bar = foo(2); // bar 现在是一个闭包
bar(10);

上述脚本最终也会弹出 16。虽然 bar 不直接处于 foo 的内部作用域中,但它仍然可以访问 x 和 tmp。



然而,由于 tmp 仍然存在于 bar 闭包的作用域内,它每次调用时都会自增 1。



(考虑到六岁孩子的理解限制:我们可以返回多个闭包方法,例如返回它们的数组,或者将它们设置为全局变量。它们都指向相同的 x 和相同的 tmp,而不是各自拥有副本。)



值传递与对象引用的区别

注意:现在来点稍微复杂一点的内容。



上面的 x 是一个字面量(值传递)。与 JS 中其他字面量一样,当调用 foo 时,实参 x 的值被复制了一份,作为 foo 的参数 x。



问题来了,JS 处理对象时使用引用传递。因此,当你调用 foo 并传递一个对象时,foo 函数返回的闭包将引用最初的那个对象!


示例代码如下:

 
function foo(x) {
var tmp = 3;
return function (y) {
alert(x + y + tmp);
x.memb = x.memb ? x.memb + 1 : 1;
alert(x.memb);
} 
} 
var age = new Number(2);
var bar = foo(age); // bar 现在是一个引用了age的闭包
bar(10);

不出意料,每次运行 bar(10),x.memb 都会自增 1。需要注意的是,x 每次指向同一个对象变量——age。运行两次 bar(10) 后,age.memb 会变成 2。



这与 HTML 对象的内存泄漏有关,不过这可能超出了当前回答的范围。



用户 JohnMerlino 对 Ali 说:



这里有一个不使用 return 关键字的闭包例子:


示例代码如下:

 
function closureExample(obj, text, timedelay) {
setTimeout(function() {
document.getElementById(objID).innerHTML = text;
}, timedelay);
} 
closureExample('myDiv', 'Closure is created', 500);

闭包的访问范围与变量维持

深夜 1:37,用户 John Pick 这样回答:



JS 中的函数可以访问它们的:



1. 参数



2. 局部变量或函数



3. 外部变量(环境变量),包括



3.1 全局变量,包括 DOM



3.2 外部函数的变量或函数。



如果一个函数访问了它的外部变量,那么它就是一个闭包。



注意,外部函数不是必需的。通过访问外部变量,一个闭包可以维持这些变量的存活。在内部函数和外部函数的例子中,外部函数可以创建局部变量,并最终退出;但是,如果任何一个或多个内部函数在它退出后却没有退出,那么内部函数就维持了外部函数的局部数据。



一个典型的例子就是全局变量的使用。



闭包的技术定义与实际应用

用户 mykhal 这样回答:



维基百科对闭包的定义如下:



In computer science, a closure is a function together with a referencing environment for the nonlocal names (free variables) of that function.



从技术角度来讲,在 JS 中,每个函数都是闭包,因为它总是能访问在它外部定义的数据。



Since scope-defining construction in Javascript is a function, not a code block like in many other languages, what we usually mean by closure in Javascript is a fuction working with nonlocal variables defined in already executed surrounding function.



闭包经常用于创建含有隐藏数据的函数(但并不总是这样)。


示例代码如下:

 
var db = (function() {
// 创建一个隐藏的object, 这个object持有一些数据
// 从外部是不能访问这个object的
var data = {};
// 创建一个函数, 这个函数提供一些访问data的数据的方法
return function(key, val) {
if (val === undefined) { return data[key] } // get
else { return data[key] = val } // set
} 
// 我们可以调用这个匿名方法
// 返回这个内部函数,它是一个闭包
})(); 
db('x'); // 返回 undefined
db('x', 1); // 设置data['x']为1
db('x'); // 返回 1
// 我们不可能访问data这个object本身
// 但是我们可以设置它的成员

看了这么多国外高手的解答,不知道你懂还是不懂,反正我是懂了。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章