美文网首页
再次理解闭包

再次理解闭包

作者: coolheadedY | 来源:发表于2017-02-28 00:04 被阅读51次

    什么是闭包

    • 一种写法
    • 在函数定义处的环境中自带数据
    • 一种为局部定义函数封装信息的方式

    参考

    闭包热身

    普通循环

    for (var i = 0; i < 5; i++) {
      console.log(i);
    } //输出0 1 2 3 4 
    

    延时循环

    for (var i = 0; i < 5; i++) {
      setTimeout(function() {
        console.log(i);
      }, 1000 * i);
    } // 因为1秒后循环已经结束输出5个5
    

    让延时循环输出0到4 (使用闭包)

    for (var i = 0; i < 5; i++) {
      (function(i) {
        setTimeout(function() {
          console.log(i);
        }, i * 1000);
      })(i); //一个立即执行函数,用到了闭包,输出0 1 2 3 4,每次循环都把i保存了下来
    }
    

    热身结束,这其实是来自知乎上面一道关于JS运行机制的文章的前半段,运行机制以后再说,有兴趣的话可以先看 运行机制

    一,变量作用域

    变量作用域有两种:全局变量和局部变量。JavaScript语言规定,在函数内部可以直接读取全局变量。

        var n=999;
        function f1(){
          alert(n);
        }
        f1(); // 999
    

    另一方面,在函数外部自然无法读取函数内的局部变量。

      function f1(){
        var n=999;
      }
      alert(n); // error
    

    注意,函数内部声明变量的时候,一定要使用var命令。如果不使用,实际上声明了一个全局变量。

        function f1(){
            n=999;
        }
        f1();
        alert(n); // 999
    

    二,如何从外部获取局部变量

    如何从外部获取局部变量,那就是在函数的内部,再定义一个函数。

    function f1(){
        var n=999;
        function f2(){
            alert(n); // 999
        }
    }
    

    在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。对上面例子变通下。

    function f1(){
        var n=999;
        function f2(){
            alert(n); 
        }
        return f2;
    }
    var result=f1();
    result(); // 999
    

    上面代码中,把函数f2作为返回值,那在函数f1外部,就可以获取它内部变量了。

    三,闭包

    上面代码中,函数f2,就是闭包。闭包就是能够读取其他函数内部变量的函数。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。闭包作用有两个:

    • 可以读取函数内部的变量
    • 这些变量始终保持在内存中

    例一:

    function f1(){
        var n=999;
        nAdd=function(){n+=1}
        function f2(){
            alert(n);
        }
        return f2;
    }
    var result=f1();
    result(); // 999
    nAdd();
    result(); // 1000
    

    上面代码中,执行函数f1 返回函数f2result = f2result实际上就是闭包f2函数,它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

    为什么会这样呢?原因就在于f1f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。(具体阅读《JavaScript-内存》一章)

    另外,nAdd=function(){n+=1} nAdd是一个全局变量,nAdd的值是一个匿名函数,这个函数本身也是一个闭包。

    四,使用闭包注意点

    • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
    • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

    例子:

     function assignHandler() {
        var el = document.getElementById('demo');
        el.onclick = function() {
            console.log(el.id);
        }
    }
    assignHandler();
    

    以上代码创建了作为el元素事件处理程序的闭包,而这个闭包又创建了一个循环引用,只要匿名函数存在,el的引用数至少为1,因些它所占用的内存就永完不会被回收。

    function assignHandler() {
        var el = document.getElementById('demo');
        var id = el.id;
    
        el.onclick = function() {
            console.log(id);
        }
    
        el = null;
    }
    assignHandler();
    

    把变量el设置null能够解除DOM对象的引用,确保正常回收其占用内存。

    五,模仿块级作用域

    任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。

    (function(){
        //块级作用域
    })();
    

    六,实际例子

    例一:

    var name = "The Window";
    var object = {
        name : "My Object",
        getNameFunc : function(){
            return function(){
                return this.name;
            };
        }
    };
    alert(object.getNameFunc()());
    

    上面代码中,执行object.getNameFunc() 返回 function(){return this.name}在执行一次,相当于执行这个返回函数,得到 this.name 这里的this代表window,所以是全局变量 name

    例二:

    var name = "The Window";
    var object = {
        name : "My Object",
        getNameFunc : function(){
            var that = this;
            return function(){
                return that.name;
            };
        }
    };
    alert(object.getNameFunc()());
    

    上面代码和例一的区别是,把this保存起来,that 代表 object,得到的是 'My Object'

    例三:(出自很多人都会做错的闭包题)

    function fun(n,o) {
        console.log(o);
        return {
            fun:function(m){
                return fun(m,n);
            }
        };
    }
    var a = fun(0);a.fun(1);  a.fun(2);  a.fun(3);//undefined,?,?,?
    var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
    var c = fun(0).fun(1);  c.fun(2);  c.fun(3);//undefined,?,?,?
    
    /*
        解题前,需要知道第一个函数fun(n,o),第二个函数 fun: function(m){return fun(m,n)},第三个函数 fun(m,n) 其中第一和第三函数相同
        匿名函数有 var fn1=function (){} ,具名函数 function fn1(){} 和 var fn1=function a(){};
    */
    
    /* 第一个
    
    1, var a = fun(0); ---->执行fun(0)后, 返回一个对象 a = {fun:function (m){return fun(m,n)}} ,此时 n = 0, o = undefined
    2, a.fun(1); ----->  返回一个函数fun(m,n),此时 m = 1, n = 0 由于闭包前面的变量不会被删除,所以fun(1,0) 执行后 console.log(o) = 0;
    3, a.fun(2), a.fun(3) 和上面一样的结果
    */
    
    /*第二个:链式调用,后面执行的函数调用前面返回的数据
    
    1, var b = fun(0) 和上面第一步一样, 返回 {fun:function (m){return fun(m,n)}}   和 n = 0, o = undefined
    2, 执行到 fun(1) 时, 返回 fun(m,n) , 此时 m = 1, n = 0 , 执行 fun(1,0) 后 console.log(o) = 0 返回 {fun:function (m){return fun(m,n)}} ,此时 n = 1, o = 0
    3, 执行到 fun(2) 时, 返回 fun(m,n) , 此时 m = 2, n = 1 , 执行 fun(2,1) 后 console.log(o) = 1 返回 {fun:function (m){return fun(m,n)}} ,此时 n = 2, o = 1
    4, 执行到 fun(3) 时, 返回 fun(m,n) , 此时 m = 3, n = 2 , 执行 fun(3,2) 后 console.log(o) = 2 返回 {fun:function (m){return fun(m,n)}} ,此时 m = 3, o = 2
    
    */
    
    /*第三个
    
    1, var c = fun(0).fun(1); 执行和第二个前两步骤一致,o = undefined 和 0
    2, 执行到 c.fun(2)时,c = {fun:function (m){return fun(m,n)}} ,此时 n = 1, o = 0, 返回 fun(2,1)后 m = 2, n = 1 ,o = 1;
    c.fun(3) 执行出来一致 o = 1;
    

    相关文章

      网友评论

          本文标题:再次理解闭包

          本文链接:https://www.haomeiwen.com/subject/rwogwttx.html