zoukankan      html  css  js  c++  java
  • 转载的一篇博客,感觉不错,自我感觉很到位,来自 http://www.cnblogs.com/laizhihui/p/5810965.html

    JavaScript 闭包总结 (深入理解)

     

    什么是闭包

    简单的说闭包就是函数里面的函数,《JavaScript高级程序设计》里是这样定义的

    闭包是指有权访问另一个函数作用域中的变量的函数。

    先看一道面试时经常被考的题目

    • 代码1:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>lzhTest</title>
    </head>
    <body>
    <ul>
        <li>0</li>
        <li>1</li>
    </ul>
    <script>
        var lis = document.getElementsByTagName("li");
        for(var i = 0; i < lis.length; i++){
            lis[i].onclick = function(event){
                alert(i);
            }
        }
    </script>
    </body>
    </html>

    分别点击 li,alert什么?答案均是 2. 为什么呢?我们接着往下看

    作用域链和活动对象

    函数被调用时会创建一个执行环境和作用域链 (scope chain),作用域链中每个元素都指向一个活动对象或变量对象 (执行环境中定义的所有变量和函数都保存在这个对象中,包括 this、arguments),函数执行完毕,作用域链被销毁,如果这时相应的变量对象没有被引用,则变量对象占用的空间会被释放
    比如上面题目中的作用域链是这样的:
    作用域链和活动对象
    匿名函数1 和 匿名函数2是两个事件处理函数,从图中可以看出,在作用域链的最前端(即下标为0)对应的活动对象中,是不存在 i 的,i 在全局变量对象中,点击的时候,需要往作用域的上层查找 i,于是就找到了全局变量对象中的 i,因为点击的时候,i 早已增加成为 2,所以 alert 的 i 均为 2。

    怎样做到每次 alert 的是下标呢?

    直接看代码:

    • 代码2

      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <title>lzhTest</title>
      </head>
      <body>
      <ul>
          <li>0</li>
          <li>1</li>
      </ul>
      <script>
          var lis = document.getElementsByTagName("li");
          var helper = function(i){
              return function(event) {
                  alert(i);
              }
          }
          for(var i = 0; i < lis.length; i++){
              lis[i].onclick = helper(i);
          }
          // 参考自:《JavaScript语言精粹》
      </script>
      </body>
      </html>
      对应的作用域链及活动对象:
      理解了这里,闭包应该掌握得差不多了
    • 代码2中,全局变量对象中 i 的变化用 0->2 表示 (代码1也如此)
    • 第一次调用 helper 时,全局变量对象中 i 是0,所以此时 helper(1) 的活动对象中 i 是0,因为是以形参的形式从全局变量对象中传进来的。此后 helper(1) 中的 i 就不变了。
    • 接着 helper(1) 中返回一个匿名函数1,(根据《JavaScript高级程序设计》介绍:函数在调用时生成作用域链和活动对象,但闭包被返回时,就会生成作用域链和活动对象(中文第3版 P180 line7),我的理解并不是这样的),返回的 匿名函数1 引用着 helper1 中的i,helper(1) 执行完毕,之后 helper(1)的执行环境和作用域链销毁,但是 helper(1) 的活动对象还在,因为 匿名函数1 的作用域链还在引用着它(按照我的理解应该是 匿名函数1 还引用着它)。(执行环境和作用域链销毁这一过程在图中没有体现出来)。
    • 如果用户 click lis[0],那么就会调用 匿名函数1,(按照我的理解:此时匿名函数1的执行环境才会被压入环境栈中,同时生成 匿名函数1 的作用域链和活动对象),alert(i) 时,因为 匿名函数1 的活动对象中找不到 i 所以往作用域链的父级找,找到了 helper(1) 活动对象中的 i,于是 alert 了 0
    • 在第二次调用 helper 时,生成的作用域链和活动对象是新的了,与 helper(1) 中的不同,同理,当用户 click lis[1] 时,alert 1,如果此时还不是很懂的话,可以回头再看看图,或者从 代码1 那里从新理解。
    • 如果有认真看的话,请思考一下我的看法和《JavaScript高级程序设计》的看法,到底哪个是正确的,我还是坚持自己的看法,如果有错的话,还请指出。

      由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。

    • 来看一个闭包内引用着活动对象中的变量时,活动对象不被释放的例子,注意看图片右侧的 scope:
      活动对象不被释放
      从图中可以看出,param1、param3 所在的活动对象不在作用域链中,应该是被通知准备回收了或者已经回收了,而 param2、param4 所在的变量对象还在。
    • 另外,如果闭包中有 eval 的话,由于不能判断 eval 里是否有引用父级作用域链活动对象中的变量,那么该作用域链中的所有活动对象都会被保留,所以在闭包中尽量不要使用 eval
      闭包中有eval
    • 但如果在里面用的是 new Function("这里引用闭包外的变量") 这种写法,如果没有其它引用,父级作用域链的活动对象是不会保留的,下面这种写法最终会报错 Uncaught ReferenceError: param1 is not defined
      闭包中有动态创建函数

    内存泄露

    如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无法被销毁,代码如下,只要匿名函数存在,element 的引用数至少也是 1,因此它所占用的内存就永远不会被回收。(书中有讨论到这是IE9以前的问题,我怎么觉得这是个普遍的问题呢,难道 Chrome 或其它浏览器中 element 的引用数至少还能是0吗?求不吝赐教)。

    function assignHandler(){
       var element = document.getElementById("someElement");
       element.onclick = function(){
           alert(element.id);
       };
    }
    • 所以《JavaScript高级程序设计》建议这么写:
    function assignHandler(){
       var element = document.getElementById("someElement");
       var id = element.id;
    
       element.onclick = function(){
           alert(id);
       };
    
       element = null;
    }

    闭包的作用

    闭包无处不在,如果真的要归纳几个用处,如下:

    模仿块级作用域

    我们都知道,在 JavaScript 中,是不存在块级作用域的,也就是在 {} 里面声明的变量,在 {} 外面依然可以访问。但如果利用函数一旦执行完,其中执行环境和作用域链均销毁的特性,我们可以这么做:

    (function(){
        // 让一个括号包着一个函数,相当于得到这个函数的引用,
        // 然后再在后面加个括号,执行这个函数,称这种函数为 立即执行函数
        // 在这里面声明的变量,外部不可访问,除非 return 一个闭包或变量
        // 如果此时外部是一个函数的话,那这个立即执行函数也是一个闭包
        // 只是这个闭包并没有返回些什么
    })();

    模块化开发

    有了上面提到的模仿块级作用域,就可以减少全局变量的使用,jQuery 就是这么做的:

    (function(window, undefined){
        // ...
        // 这里实现 jQuery 的所有功能
        // 势必会声明很多变量,如果暴露在全局作用域中会造成命名冲突
        // 所以用一个立即执行函数包起来,但是为什么要传入 window 呢
        // 传入 window 是为了让 window 成为当前作用域下的变量,这样减少访问成本,或者还有其它功能
        // 传入 undefined 的原因:
        // 在低版本的 IE 中,undefined 是可写的,有可能 undefined 就不是 undefined 了
        window.jQuery = window.$ = jQuery;
        // 这里的 jQuery 就是一个函数,平时我们用的时候是 $(参数、参数),所以,他也是个闭包嘛
        // 上面是模块中的一种,而旦还有更高级的,jQuery 兼容 AMD 规范
        if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
            define( "jquery", [], function () {
              // AMD 这里不详细介绍了,这里就是 define 一个模块
              // 这里也是一个闭包
              return jQuery; // 这里就是闭包中的闭包了
          } );
        }
    }(window))
  • 相关阅读:
    jProfiler远程连接Linux监控jvm的运行状态
    Linux性能监控分析命令(五)—free命令介绍
    Linux性能监控分析命令(三)—iostat命令介绍
    Python编程练习题学习汇总
    OSPF与ACL综合应用实验
    ACL基础知识点总结
    NAT基础知识点
    VLAN实验五:利用三层交换机实现VLAN间路由
    VLAN实验四:利用单臂路由实现VLAN间路由
    VLAN实验三:理解Hybrid接口
  • 原文地址:https://www.cnblogs.com/haotian-dada666/p/5812296.html
Copyright © 2011-2022 走看看