《你不知道的JavaScript》之作用域闭包

闭包

说到作用域闭包,我想很多同学都知道,但是让你讲讲其原理以及应用场景,也许又不知从何说起。

其实作用域闭包无处不在,只是你自己没意识到。

简单来说,函数能够记住并可以访问所在的词法作用域时,便产生了闭包。看过<<JavaScript 高级程序设计>>的同学也可能会这样说,闭包就是定义在函数里的函数喽。其实这两种解释是一个意思,为什么这么说呢,我们先来看段代码


function foo(){

        var a = 2;

        function bar(){

              console.log(a);

        }

        return bar;

}

var test = foo();

test();//输出结果为2


看完这段代码你有发现什么问题吗?函数bar竟然在其所在的作用域外部被调用了。按道理来说,bar涵盖了foo定义的作用域,也只能在函数内部被调用。之所以在外部能被调用,是因为首先将bar标识符当做变量被foo返回,当 foo() 执行后,bar便赋值给了test,test的调用就是对bar的调用,这个过程实质上就是通过不同的标识符引用调用了内部的bar。

当 foo() 执行后,理论上内部的作用域是要被销毁的,因为引擎有垃圾回收器通过标记清除来回收不再使用的内存空间。但事实上,foo的内部作用域一直存在,被谁占用着呢?就是这个内部函数bar(),拜它声明的位置所赐,它依然保持着对foo内部作用域的引用,这个引用其实就是闭包。因此,后续的 test 才能够执行,并能够访问其所在作用域中的变量a。

其实说白了,如果将函数作为参数并到处传递,其所涵盖的作用域(例如其所在的函数构建的作用域)就一直在那里。所以像定时器、事件监听器、Ajax请求等,只要应用了回调函数的地方,我们都可以认为是在使用闭包。

循环和闭包

说到闭包,另一个典型的例子就是for循环了,看下面这段代码:


for( var i = 1; i <= 5; i ++){

      setTimeout(function(){

            console.log(i);

       },i*1000);

}


上面代码会输出什么结果呢?是 1 2 3 4 5吗?不是的,其实是以每1秒的频率输出5次6,这是因为第一、延迟函数 setTimeout 里面定义的回调函数必须等到循环结束时才会调用,这个时候 i 已经变成6,;第二、每一次循环都会定义一个延迟函数,这样的话回调函数要调用5次。

不过,我们的真实的目的是想按顺序输出数字对吧,怎么办呢?有人可能很快想到了用作用域包起来啊,聪明的你怎么写代码呢?


for( var i = 1; i <= 5; i++){

   (function(){

           var j = i;

           setTimeout(function(){

            console.log(i);

          },i*1000);

    })();

}


仅仅是包起来还不够,还要添加点代码。在每个作用域内部定义一个属于自己的变量 j,这样,任你外部的 i 再怎么变化,我已经有了自己的专属变量 j,这样就可以达到目的了。

当然,代码还可以改成下面这样:


for( var i = 1; i <= 5; i ++){

   (function(){

           setTimeout(function(j){

            console.log(i);

          },i*1000);

    })(i);

}


回到块作用域

我前面的文章有提到过块作用域,其中ES6新引入的 let 就可以劫持块作用域,并在这个作用域中声明一个变量。本质上就是将块作用域转化成封闭的作用域了。因此,我们可以写出下面的代码,照样能够按顺序输出数字。


for( var i = 1; i <= 5; i++){

           let j = i;

           setTimeout(function(){

            console.log(j);

          },i*1000);

}


当然,还有一种情形就是for循环的头部用let 声明(如下代码),这样它会被赋予一种特殊行为,就是每次循环 ,i 都会被声明一次,然后都会用上一次迭代结束的值赋予给 i,所以同样可以达到目的。


for( let i = 1; i <= 5; i++){

           setTimeout(function(){

            console.log(i);

          },i*1000);

}


是不是觉得很酷、很神奇呢,块作用域和闭包联手起来原来这么厉害啊。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值