作用域闭包

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。 

function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
baz();   //2    -------->这就是闭包的效果

函数bar的词法作用域能够访问foo()的内部作用域。然后我们将bar()函数本身当作一个值类型进行传递。在这个例子中,我们将bar所引用的函数对象本身当作返回值。

在foo()执行过后,其返回值(也就是内部的bar函数)赋值给变量baz并调用baz(),  实际上只是通过不同的标识符引用了内部的函数bar()。

bar()显然可以被正常执行。但是在这个例子中,他在自己定义的词法作用域以外的地方执行。

在foo()执行后,通常会期待foo()的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器来释放不再使用的内存空间。由于看上去foo()的内容不会再被使用,所以很自然地会考虑对其进行回收。

而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然处在,以此没有被回收。谁在使用这个内部作用域? 原来是bar本身在使用。

拜bar()所声明的位置所赐,它拥有涵盖foo()内部作用域的闭包,使得改作用域能够一直存活,以供bar在之后任何时间进行引用。

bar()依然持有对该作用域的引用,而这个就叫做闭包。

当然,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。

function foo(){
var a = 10;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
fn();    //这就是闭包
}

传递函数当然也可以是间接的:

var fn;
function foo(){
var a = 10;
function baz(){
console.log(a);
}
fn = baz;   //将baz分配给全局变量
}
function bar(){
fn();    //这就是闭包
}


foo();
baz();   //2

无论通过何种手段将内部函数传递到所在的词法作用域以外,他就会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

那么再看下面的一个例子:

function wait(msg){


   setTimeout(function timer(){


    console.log(msg);


   }, 1000)


}

wait("hello,closure");

将一个内部函数(名为timer)传递给setTimeout(..)。timer具有涵盖wait(..)作用域的闭包,因此还包有对变量msg的引用。

或者,如果你很熟悉jQuery,可以思考下面的代码:

function setBot(name, selector){
$(selector).click(
function activetor(){
console.log("activetor" + name);
}
)
}

其实是将内部函数activetor传递到所在的词法作用域之外执行,就产生了闭包。

在定时器,事件监听器,ajax请求,夸窗口通信,WebWorkor或者任何其他的异步任务中,只要使用了回调函数,实际上就是在使用闭包!!!

 循环和闭包

for(var i = 1; i <= 5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000)
}

这段代码在运行时会以每秒一次的频率输出五次6; 原因如下:

setTimeout是异步任务,而for循环是同步任务,for循环一瞬间执行完,与此同时浏览器会启动五个定时器进行计时,依次在1,2,3,4,5秒之后将timer函数放到异步任务队列里面,在执行timer函数的时候,for循环早已结束,而结束时i = 6;  timer持有对原始作用域的引用,而原始作用域的i=6, 所有会输出五次6;

那么接下来再看下面的这个例子:

for(var i = 1; i <= 5; i++){
(function(j){
setTimeout(function timer(){
console.log(j);
}, j*1000)
})(i);
}

上面的代码执行结果为每隔一秒以次输出1,2,3,4,5

timer持有对原始作用域的引用,而(function(){})()立即执行函数会形成自己独立的作用域,在这个作用域内部,j的值依次为1,2,3,4,5

换句话说,每次迭代我们都需要一个块级作用域。ES6里面let可以用来劫持块级作用域,并且在这个作用域中声明一个变量。本质上这是将一个块转换成一个可以被关闭的作用域。

for(var i = 1; i <= 5; i++){
let j = i;
setTimeout(function timer(){
console.log(j);
}, j*1000);
}

当然了,你还可以这样写:

for(let i = 1; i <= 5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000);
}

注意:let在每次迭代都会声明。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值