理解 JavaScript 中的闭包
栏目: JavaScript · 发布时间: 6年前
内容简介:继上一篇《理解 JavaScript 中的作用域》后,我又立刻写下了这篇文章,因为这两者是存在关联的,在理解闭包前,你需要知道作用域。而对于那些有一点 JavaScript 使用经验的人来说,理解闭包可以看做是某种意义上的重生,但这并不简单,你需要付出非常多的努力和牺牲才能理解这个概念。如果你理解了闭包,你会发现即便是没理解闭包之前,你也用到了闭包,但我们要做的就是根据自己的意愿正确地识别、使用闭包。
继上一篇《理解 JavaScript 中的作用域》后,我又立刻写下了这篇文章,因为这两者是存在关联的,在理解闭包前,你需要知道作用域。
而对于那些有一点 JavaScript 使用经验的人来说,理解闭包可以看做是某种意义上的重生,但这并不简单,你需要付出非常多的努力和牺牲才能理解这个概念。
如果你理解了闭包,你会发现即便是没理解闭包之前,你也用到了闭包,但我们要做的就是根据自己的意愿正确地识别、使用闭包。
什么是闭包
闭包的定义,你需要掌握它才能理解和识别闭包:
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即便函数是在当前词法作用域之外执行。
下面用一些代码来解释这个定义:
function foo(){
var a = 2;
function bar(){
console.log(a); // 2
}
bar();
}
foo();
复制代码
很明显这是一个嵌套作用域,而 bar
的作用域也确实能够访问外部作用域,但这就是闭包吗?
不,不完全是,但它是闭包中很重要的一部分:根据词法作用域的查找规则,它能够访问外部作用域。
下面再来看这段代码,它清晰地使用了闭包:
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
baz(); // 2 —— 这就是闭包
复制代码
由于 bar
的词法作用域能够访问 foo
的内部作用域,然后我们把 bar
这个函数本身当作返回值,然后在调用 foo
时把 bar
引用的函数赋值给 baz
(其实是两个标识符引用同一个函数),所以 baz
能够访问 foo
的内部作用域。
而这里正是印证前面的定义:函数是在当前词法作用域之外执行。
其实按正常情况下,引擎有垃圾回收器用来释放不再使用的内存空间,当 foo
执行完毕时,自然会将其回收,但闭包的神奇之处正是可以阻止这件事情的发生,因为内部作用域依然存在, bar
在使用它。
由于 bar
声明位置的原因,它涵盖了 foo
内部作用域的闭包,使得该作用域能够一直存活,以供 bar
在之后任何时间进行引用。
bar
依然有对该作用域的引用,而这个引用就叫做闭包。
因此,当 baz
在调用时,它自然能够访问到 foo
的内部作用域。
当然,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包的存在:
function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
fn(); // 2 —— 这也是闭包
}
复制代码
把内部函数 baz
作为 fn
参数传递给 bar
,当调用 fn
时,它能够访问到 foo
的内部作用域。
传递函数也可以是间接的:
var fn;
function foo(){
var a = 2;
function baz(){
console.log(a);
}
fn = baz;
}
foo();
fn(); // 2 —— 这也是闭包
复制代码
所以:
无论通过何种方式将内部函数传递到所在的词法作用于之外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。
闭包的使用
既然前面说闭包无处不在,那不妨看看几个平时经常看到的片段,看看闭包的妙用。
function wait(message){
setTimeout(function timer(){
console.log(message);
},1000);
}
wait("Hello, closure!");
复制代码
将一个内部函数(这里叫做 timer
)作为参数传递给 setTimeout
,而 timer
能够访问 wait
的内部作用域。
如果你使用过 jQuery
,不难发现下面代码中也使用了闭包:
function setupBot(name,selector){
$(selector).click(function activator(){
console.log("Activating:" + name);
})
}
setupBot("Closure Bot 1","#btn_1");
setupBot("Closure Bot 2","#btn_2");
复制代码
本质上无论何时何地,如果将函数( 访问它们各自的词法作用域)当作第一级的值类型并到处传递, 你就会看到闭包在这些函数中的应用。 在定时器、 事件监听器、Ajax请求、 跨窗口通信、Web Workers或者任何其他的异步( 或者同步)任务中, 只要使用了回调函数,实际上就是在使用闭包!
再来看一个很经典的闭包面试题:
for (var i=1; i<=5; i++){
setTimeout(function(){
console.log(i);
},i*1000);
}
复制代码
正常情况下,我们对这段代码行为的预期是每秒一次输出1~5。
但实际上,这段代码在运行时会以每秒一次的频率输出五次6。
为什么?
首先解释6是从哪里来的,这个循环的终止条件是 i
不再 <=5
,所以当条件成立时, i
等于6。因此,输出显示的是循环结束时 i
的最终值。
也就是我们陷入了一个这样的误区:以为循环中每个迭代在运行时都会复制一个 i
的副本,但根据作用域的工作原理,它们都共享同一个全局作用域,因此实际上只有一个 i
。
要使这段代码的运行与我们预期一致,解决方法如下:
for (var i=1; i<=5; i++){
(function(j){
setTimeout(function(){
console.log(j);
},j*1000);
})(i)
}
复制代码
在这段代码中我们使用了 IIFE
,将 i
作为参数 j
传递进去,在每个迭代 IIFE
会生成一个自己的作用域,它们接受参数 j
不一样,所以这段代码能够符合我们预期地运行。
还有别的解决方案吗?
是的,使用 ES6 新出的 let
可以解决这个问题:
for (let i=1; i<=5; i++){
setTimeout(function(){
console.log(i);
},i*1000);
}
复制代码
我们仅仅把 var
替换为 let
就轻松地解决了该问题,原因如下:
-
for中有自己的块作用域(()是父级作用域,{}是子级作用域)。 -
使用
let能够创建块作用域的变量。
好了,到现在你应该能够很容易地识别闭包,那么接下来,我们继续介绍闭包更高级的用法。
假设我们有这样一个对象:
var box = {
age : 18,
}
console.log(box.age); // 18
复制代码
然而这里有一个问题,那就是属性 age
可以随意改变,如果我们使用闭包,就可以实现私有化,将 age
属性保护起来,只做允许的修改。
var box = (function (){
var age = 18;
return {
birthday : function(){
age++;
},
sayAge : function(){
console.log(age);
}
}
})();
box.birthday();
box.sayAge(); // 19
复制代码
这样我们就保证 age
属性只能增加,而不能减少,毕竟没有人能够越活越年轻。
注意:
- 其实对象也有方法可以控制属性的修改,但这里主要讲述闭包,就不过多赘述。
- 使用闭包能够轻松实现原本在 JavaScript 较复杂的设计。
后记
其实当你理解了闭包之后,你就会发现一切都是那么的理所当然,就仿佛它本该如此。
最后,如果你已经理解了闭包并且想练习一下,那么我可以出一道题目给你:
实现一个 add
函数,功能: add(1)(2)(3); // 6
难一点的:
实现一个 add
函数,功能: add(3)(‘*’)(3); // 9
有几点:
add
感谢观看!
注:此文为原创文章,如需转载,请注明出处。
以上所述就是小编给大家介绍的《理解 JavaScript 中的闭包》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 理解 JavaScript 闭包
- 理解JavaScript的闭包
- 简单理解 Scala 的闭包
- [译] 理解 Rust 中的闭包
- [译]理解JS中的闭包
- [译] 我从没理解过 JavaScript 闭包
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
GUI设计禁忌2.0
Jeff Johnson / 盛海艳 等 / 机械工业出版社 / 2008 / 49.00元
本书描述软件开发人员在设计图形用户界面(GUI)时经常犯的“禁忌”,并提出避免这些错误的基本原则和理论依据。本书将GUI禁忌分为7种类型:GUI控件禁忌、导航禁忌、文字禁忌、图形设计和布局禁忌、交互禁忌、响应性禁忌以及管理禁忌,并分别进行详述。 本书编排独特,条理清晰,针对性极强,是不可多得的GUI设计优秀资源。本书适合软件开发人员、web站点设计人员、开发经理、用户界面设计人员等阅读。一起来看看 《GUI设计禁忌2.0》 这本书的介绍吧!
URL 编码/解码
URL 编码/解码
RGB HSV 转换
RGB HSV 互转工具