最近在看一本书《你不知道的JavaScript(上)》,这本书里第5章:作用域闭包,是关于闭包的介绍,讲的特别好。看完这个介绍,我觉得有必要整理下我对闭包这个概念的理解,因为之前我对闭包的理解比较模糊。
下定义
闭包是什么?对这个概念下一个定义。
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即函数是在当前词法作用域之外执行。
这里有一个概念,词法作用域,这又是什么? 看下面代码
function foo(){
var a = 2;
function bar(){
console.log(a);
}
}
简单来说词法作用域就是,当前变量或者函数所处的位置所在的作用域。从上面的代码来看,bar函数的词法作用域就是foo函数内部这个作用域。
举例说明什么是闭包
例1:
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
baz(); //2 ------这就是闭包的效果,从foo函数的外部可以访问它内部的变量2
说明:调用foo()函数会得到一个bar函数的引用,又把这个引用赋值给baz,当调用baz()时,其实是调用了bar(),从闭包的定义来看,bar函数是在它的当前词法作用域(foo函数包裹的作用域)之外执行了,所以产生了闭包。
例2:
function foo(){
var a = 2;
function baz(){
console.log(a); //2
}
bar( baz );//把baz函数当作参数
}
function bar(fn){
fn(); //这里产生了闭包
}
foo();//调用foo函数
说明:当调用foo函数时,foo里面又调用了bar函数,并且把baz函数作为参数传递给了bar,但是bar函数是在baz的词法作用域外面的,也就是说baz函数在它的词法作用域外面被调用了,从闭包的定义来看,这里产生了闭包。
例3:
var fn;
function foo(){
var a = 2;
function baz(){
console.log(a);
}
fn = baz; //将baz赋值给全局变量fn
}
function bar(){
fn(); //这就是闭包
}
foo();
bar(); //2
上面这个例3也产生了闭包。无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。
闭包的副作用
我们都知道JavaScript的内存管理方式是通过它自己的垃圾回收器来实现的。 用上面例1的代码来看,在foo函数执行后,通常会期待foo的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间。但是由于有闭包的存在,可以阻止这件事的发生。事实上,内部作用域任然存在,因此没有被回收。谁在使用这个内部作用域呢?是bar()本身在使用。
for循环与闭包
下面是一道好玩的题目:每隔一秒打印一个数,数字是1-5 首先是我们会想到这么实现,代码如下
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
//console.log('out:' + i)
但是这种实现得出的结果是错的。这段代码输出的结果是:每隔一秒输出一个6,一共输出5个6
下面是第一种解决方法:
//ES5的解决方案
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
//console.log('out:' + i) //假如这句不注调的话,答案:先输出6,然后每隔一秒输出1,2,3,4,5
下面是第二种解决方法:
//ES6的解决方案,利用let
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}