闭包及一些demo

时光总嘲笑我的痴心妄想 提交于 2019-11-28 00:41:26

在一个函数b内定义的一个函数a里,a使用了其父级b的变量或者函数。这样闭包便形成了!!

作用: 1. 获取函数内部的变量 2.将变量保存在内存中。3.定义私有变量,避免全局变量污染

注意闭包一定是需要的时候使用,勿滥用,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响!

下面例子来源
MDN
阮一峰
廖雪峰

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JS闭包Closures</title>
</head>
<body>
    <div>
        <p id="help">Helpful notes will appear here</p>
        <p>E-mail: <input type="text" id="email" name="email"></p>
        <p>Name: <input type="text" id="name" name="name"></p>
        <p>Age: <input type="text" id="age" name="age"></p>
    </div>
</body>
<script>
    //-------------------------------------------------------------------------------
    //闭包
    //闭包是由函数以及创建该函数的词法环境组合而成。
    //这个环境包含了这个闭包创建时所能访问的所有局部变量

    function makeAdder(a) {
        return function (b) {
            return a + b;
        }
    }
    var add5 = makeAdder(5);
    var add20 = makeAdder(20);
    add5(6); // ? 5+6 11
    add20(7); // ? 20 +7 27
    /*
        当调用 makeAdder 时,解释器创建了一个作用域对象,它带有一个属性:a,这个属性被当作参数传入 makeAdder 函数。
        然后 makeAdder 返回一个新创建的函数(暂记为 adder)。
        通常,JavaScript 的垃圾回收器会在这时回收 makeAdder 创建的作用域对象(暂记为 b),
        但是,makeAdder 的返回值,新函数 adder,拥有一个指向作用域对象 b 的引用。
        最终,作用域对象 b 不会被垃圾回收器回收,直到没有任何引用指向新函数 adder。

        作用域对象组成了一个名为作用域链(scope chain)的(调用)链。
        它和 JavaScript 的对象系统使用的原型(prototype)链相类似。
    */
    //闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。
    
    function demo07() {
        var Counter = (function () {
            var privateCounter = 0;
            function changeBy(val) {
                privateCounter += val;
            }
            return {
                increment: function () {
                    changeBy(1);
                },
                decrement: function () {
                    changeBy(-1);
                },
                value: function () {
                    return privateCounter;
                }
            }
        })();

        console.log(Counter.value()); /* logs 0 */
        Counter.increment();
        Counter.increment();
        console.log(Counter.value()); /* logs 2 */
        Counter.decrement();
        console.log(Counter.value()); /* logs 1 */
        /*
            创建了一个词法环境,为三个函数所共享:Counter.increment,Counter.decrement 和 Counter.value。

            该共享环境创建于一个立即执行的匿名函数体内。
            这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。
            这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。
        */
    }
    // demo07();//闭包示例
    function demo08() {
        var makeCounter = function () {
            var privateCounter = 0;
            function changeBy(val) {
                privateCounter += val;
            }
            return {
                increment: function () {
                    changeBy(1);
                },
                decrement: function () {
                    changeBy(-1);
                },
                value: function () {
                    return privateCounter;
                }
            }
        };

        var Counter1 = makeCounter();
        var Counter2 = makeCounter();
        console.log(Counter1.value()); /* logs 0 */
        Counter1.increment();
        Counter1.increment();
        console.log(Counter1.value()); /* logs 2 */
        Counter1.decrement();
        console.log(Counter1.value()); /* logs 1 */
        console.log(Counter2.value()); /* logs 0 */
        /*
            你也许注意到了,两个计数器里面的变量是独立的,互不影响。
            每次调用一个计数器时,是通过改变变量的值,进而改变这个闭包的此法环境,但是不影响其他闭包的变量
        */
    }
    //循环闭包常见错误
    demo09();
    function demo09() {//这样写会始终提示 age  xxx
        /*
            原因: 因为onfocus以及她所在的词法作用域形成了闭包,三个元素有三个闭包,共享同一个词法作用域里
                该域里有变量item,在触发事件之前for循环早已结束。item早已指向最后一个!!!
        */
        function showHelp(help) {
            document.getElementById('help').innerHTML = help;
        }

        function setupHelp() {
            var helpText = [
                { 'id': 'email', 'help': 'Your e-mail address' },
                { 'id': 'name', 'help': 'Your full name' },
                { 'id': 'age', 'help': 'Your age (you must be over 16)' }
            ];

            // debugger
            for (var i = 0; i < helpText.length; i++) {
                var item = helpText[i];
                document.getElementById(item.id).onfocus =function () {
                        showHelp(item.help)
                }
                // document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
            }
        }
        /*
            当内部函数用到外部函数的参数时,该参数不会被js垃圾回收机制回收。
            // 解决方法0: 使用更多闭包,效果同下面方法一样
                for (var i = 0; i < helpText.length; i++) {
                    var item = helpText[i];
                    document.getElementById(item.id).onfocus =(function (help) {
                        return function(){
                            showHelp(help)
                        } 
                    })(item.help)
                }
            // 解决方法1:使用更多闭包,showhelp引用了外部变量help,形成了闭包,
            //而且其词法作用域均独立,循环传入的值被分别保存在内存中,不再指向最后一个。
                function makeHelpCallback(help) {
                    return function() {
                        showHelp(help);
                    };
                }

                // function setupHelp() {
                //     for (var i = 0; i < helpText.length; i++) {
                //         var item = helpText[i];
                //         document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
                //     }
                // }

            //解决办法2: 匿名闭包
                for (var i = 0; i < helpText.length; i++) {
                    (function() {
                    var item = helpText[i];
                    document.getElementById(item.id).onfocus = function() {
                        showHelp(item.help);
                    }
                    })(); // 马上把当前循环项的item与事件回调相关联起来
                } 
            
            //解决办法3: item前的关键字使用let,让每个闭包都使用了块级作用域的变量    
                for (var i = 0; i < helpText.length; i++) {
                    let item = helpText[i];
                    ...
                }
            //解决办法4: 绑定到某个对象上
                for (var i = 0; i < helpText.length; i++) {
                var item = helpText[i];
                document.getElementById(item.id).help = helpText[i].help;
                document.getElementById(item.id).onfocus =function () {
                        showHelp(this.help)
                }
            }    
        */
                       
        setupHelp();
    }
    //如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,
    //因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响

    //阮一峰
    /*
        在函数内部声明变量时一定要使用var命令,否则会成为全局变量
    
    */
    function f1() {
        b = 22;
    }
    f1();
    console.log(b)
    //外部读取局部变量,利用return
    function f12() {
        function f11() {
            var n = 999;
            function f2() {
                alert(n);
            }
            return f2;
        }
        var result = f11();
        result(); // 999
        /*
            能够读取其他函数内部变量的函数,因为只有函数内部的子函数才能读取内部变量
            那么闭包可以理解为定义在函数内部的函数,且引用了函数同级的变量!
            用处: 1.读取函数内部变量; 2.让变量的值保存在内存中;
        */
    }
    // f12();

    function f13() {
        function f1() {
            var n = 999;
            nAdd = function () { n += 1 }
            function f2() {
                console.log(n);
            }
            return f2;
        }
        var result = f1();
        result(); // 999
        nAdd();
        result(); // 1000
        /*
            f1()是一个闭包函数,被赋给了一个全局变量result,f2则始终在内存中,而f2依赖于f1,所以f1也在内存中,
                f1运行了两次,999,1000。这也佐证了n未被垃圾回收机制回收
            注意,nAdd没有var申明,为全局变量,其值又是一个匿名函数,也形成了闭包,nAdd就相当于setter,
                可在函数内部对函数内部的变量进行操作!  

             1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,
             否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

            2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,
                把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),
                这时一定要小心,不要随便改变父函数内部变量的值。  
            匿名函数的this指向window       
        */
    }
    // f13();
    
    /*
        他人博客: 在你写代码时将变量和块作用域写在哪里来决定,也就是词法作用域是静态的作用域,在你书写代码时就确定了
    */
    //保存一个私有变量
    function f14() {
        'use strict';
        function create_counter(initial) {
            var x = initial || 0;
            return {
                inc: function () {
                    x += 1;
                    return x;
                }
            }
        }
        var counter = create_counter();
        counter.inc();//1
        counter.inc();//2
    }
    // f14();
    function count() {
        var arr = [];
        // debugger
        for (var i = 1; i <= 3; i++) {//改为let及正常打印
            arr.push(function () {//这里push的是函数
                return i * i;
            });
            console.log(i,arr);
        }
        return arr;
    }

    var results = count(); 
    var f1 = results[0]; //打印16,因为循环到i=3时,结束循环,进入循环的只有i<=3满足的时候,
    //第四次循环i=4,还是会进入判断i<=3,结束循环!
    //var声明了i,i++在结束后还是要执行的,但是i=4不进入循环了,
    var f2 = results[1]; 
    var f3 = results[2];
    
    //下面例子是廖雪峰老师,效果出来了,大概看了一下,没有深究
    function f16() {
        'use strict';
        // 定义数字0:
        var zero = function (f) {
            return function (x) {
                return x;
            }
        };
        // 定义数字1:
        var one = function (f) {
            return function (x) {
                return f(x);
            }
        };
        // 定义加法:
        function add(n, m) {
            return function (f) {
                return function (x) {
                    return m(f)(n(f)(x));
                }
            }
        }
        var two = add(one,one);
        (two(
            function() {
                console.log('print two times?')    
            }
        ))();
        var arr1 = [];
        for(let i =0;i<4;i++){
            arr1[i] = function(){
                return i;
            };    
        }
        console.log(arr1[0]());
    }
    // f16();
    //settimeout(异步)产生闭包在循环中的问题,和上面绑定事件差不多
    function f17() {
        for (var i = 0; i < 4; i++) {
            setTimeout(function() {
                console.log('origin: '+i);
            }, 300);
        }
        for (var i = 0; i < 4; i++) {
            setTimeout((function(num) {
                return function(){
                    console.log('--------settime里增加闭包-------'+num);
                }
            })(i), 300);
        }
        for (var i = 0; i < 4; i++) {
            (function(num){
                setTimeout(function() {
                    console.log('-----------外部匿名闭包---------------'+num);
            }, 300);
            })(i)
        }
        for (var i = 0; i < 4; i++) {
            (function(){
                var temp = i;
                setTimeout(function() {
                    console.log('--------外部匿名闭包(重定义值接收)----------'+temp);
            }, 300);
            })()
        }
    }
    f17();
</script>

</html>

时隔一年再看闭包,理解的稍微深刻一些了,还会再看的!!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!