我又来了,隔了这么久才写第二篇,拖延症的我~~~
闭包
定义
闭包是个老生常谈的话题了,网上也有一大堆相关的文章,不过既然是笔记,那也简单提一下吧。
先看wiki中闭包的定义:
闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
看定义,有两个重点,自由变量和函数。那什么是自由变量?
先看个最简单的闭包例子:
function foo(){
var a = 2;
return function bar(){
console.log(a)
};
}
var baz = foo();
baz();//2
想必你也在各种文章看到过类似的例子,那我们就把它往定义中套一套。
我在上篇阅读笔记中说过,内层作用域可以访问到外层作用域的变量。没错,这个bar
访问到的外部变量a
,相对于bar
来说就是一个自由变量。这其实也就满足了最广义的闭包定义了,但我们js中通俗意义上的闭包是还要满足后面这句
这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
离开了创造它的环境。是的,经过foo
那条语句的执行,bar
已经被返回并赋值到baz
中,也就是暴露在了全局作用域中,离开了创造它的环境。
这个被引用的自由变量将和这个函数一同存在。也就是说,变量a
(连同整个内部作用域)并不会因为foo
被执行完了就消失,而是会保留在内存中。
就像下面的例子
function foo(){
var a = 2;
return function bar(){
console.log(a++)
};
}
var baz = foo();
baz();//2
baz();//3
baz();//4
baz();//5
在bar
中执行了a++
,你会看到,每次运行baz
后a
的值是累加的,而不是2
。
综上,这个baz
就是我们通常所说的闭包了。
常见闭包
一个很常见的例子就是在for循环里使用闭包
如下面
for(var i= 1; i <= 5; i++){
setTimeout( function timer(){
console.log(i);
},i*1000);
}
你可会以为这段代码是以一秒的间隔输出1~5
。
但实际上,这段代码在运行时会以每秒一次的频率输出五次6
。
6
是怎么来的?在循环结束后,i
的值为6
。也就是说,timer
里面的打印i
的在循环结束后的i
。
仔细想想也的确如此,setTimeout
的回调是在循环结束后才调用的,我们期望每次循环中会有一个i
的副本被保存timer
中,以至于在后面输出,但事实上for
循环并没有提供这样的机制,所以每次输出的i
都是在循环结束后的值6
,i
是存在于全局作用域中被共享的。
这个时候可以用闭包解决
for(var i= 1; i <= 5; i++){
(function(){
var j = i;
setTimeout( function timer(){
console.log(j);
},j*1000);
})();
}
或者更常见的写法是这样
for(var i= 1; i <= 5; i++){
(function(i){
setTimeout( function timer(){
console.log(i);
},i*1000);
})(i);
}
我们用一个立即执行的匿名函数来构造一个封闭的内部作用域,复制一个i
的副本,与timer
一起构成一个闭包,从而达到每次保存i
的值的目的。
这便是闭包的常见用法。
PS:ES6中,我们可以直接用
let
来代替var
,生成块级作用域,达到同样的效果而不用闭包。
总结
无论你懂不懂闭包,我想你的代码中已经有意无意地存在了闭包。
闭包作用有好多,变量封装,私有化;函数嵌套;函数可以很方便地访问到外部变量等。但不合理的应用也会很容易造成内存泄漏,代码混乱等问题。
重点是要理解闭包及其背后的作用域机制,这样才能更好用闭包来为我们服务。