• 从循环添加事件谈起对JS闭包的理解


    1.引子

    相信很多初学js的人,都遇到这样一种情况:想要给一堆按钮添加各自的事件,比如点击第i个按钮时,弹出i这个值。理所当然地,我们会这样写:

    1 var buttons = document.getElementsByTagName("button");  //假设一共有8个按钮
    2 for(var i = 0; i < buttons.length; i++) {
    3     buttons[i].onclick = function() {
    4         alert("我的index是"+i);
    5     } 
    6 }

    然而结果却与想象中不同。无论点击哪个按钮,弹出的都是最后的一个i的值,为什么呢?

    对于这个问题,我自己的理解是这样的:

    JS的有两种变量作用域:全局作用域和函数作用域。进行循环之后,i的值变成了8。这里的i是位于全局作用域,因此,每当我们点击任何一个按钮时,调用那个匿名函数,里面的i永远是等于8的,弹出的值自然也等于8了。

    那么如何达到我们想要的效果呢?这时候就需要用到闭包了。

    2.什么是闭包

    网上一个比较好的关于闭包的解释是这样的:

    能够读取其他函数内部变量的函数

    光看概念是难以理解的,让我们看看下面的一段代码:

     1 function a() {
     2     var i = 0;
     3     function b() {
     4         ++i;
     5         alert(i);
     6     }
     7     return b;
     8 }
     9 var c = a();
    10 c(); //1
    11 c(); //2

    在上面这段代码中,b是函数a的内部函数,但b被外部的一个变量c引用着,函数b又引用着变量i。在这种情况下,由于函数a内部的变量i被引用着,即使a这个函数执行完毕,a内部的变量i依然不会被js的垃圾回收机制回收,依然能够被引用。

    由此我们也发现了闭包的两大用处:

    1.可以读取函数内部的变量(在上面的例子中,我们可以在全局读取到函数a内部的变量)

    2.让这些变量的值始终保持在内存中(i被b引用,b又被外部变量c引用,因此i始终在内存中)

    看到这里,我们大概也能理解闭包这个定义了——“能够读取其他函数内部变量的函数”。把这句话放在上面的例子中,“能够读取函数a内部变量i的函数c”。

    上面提到的,使用闭包可以让变量的值始终保存在内存中,我们不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

    3.从垃圾回收机制理解闭包

    在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。

    4.回到一开始的例子

    理解闭包之后,我们就可以将这个东西应用到我们一开始的情况中,我们需要把要弹出的值一直保存在内存中,并保持着引用关系,实现方法如下:

    1 for(var i = 0; i < buttons.length; i++) {
    2      buttons[i].onclick = (function(k) {
    3          return function() {
    4              alert("我的index是" + k);
    5          }     
    6      })(i);
    7  }

    我们把i当作参数传入一个函数内部,然后在函数内部返回另一个函数,并用buttons[i].onclick引用这个返回的函数,使内部变量k保存在内存中,就能实现我们想要的效果了。

    5.总结

    我比较喜欢的闭包的一种解释是:某个函数【需要用到的变量在外层的函数里】,这个函数就拉着这个变量组成了闭包(来自知乎)。在上面的例子中,函数就是指b,变量就是指i,而形成的这个闭包,我们赋给了c,我们就可以愉快地利用c去使用这个闭包了。

    希望上面的文字也能起到这样提醒作用:除了闭包本身,我们也可以从js的作用域和js的垃圾回收机制去理解它。

    本文章部分内容转自:http://www.cnblogs.com/xiangqianjin/p/6595115.html

  • 相关阅读:
    [转] Actor生命周期理解
    [转] Linux History(历史)命令用法 15 例
    [转] CDH6 安装文章链接收集
    [转] org.scalatest.FunSuite Scala Examples
    [转] Mock以及Mockito的使用
    关于 maven 打包直接运行的 fat jar (uber jar) 时需要包含本地文件系统第三方 jar 文件的问题
    [转] flume使用(六):后台启动及日志查看
    [转] etcd 搭建与使用
    [转] 2018年最新桌面CPU性能排行天梯图(含至强处理器)
    让 Linux grep 的输出不换行
  • 原文地址:https://www.cnblogs.com/fengziwei/p/8602712.html
Copyright © 2020-2023  润新知