JavaScript中的闭包与立即执行函数

版权声明:欢迎转载 转载请声明出处 https://blog.csdn.net/Surrin1999/article/details/84070414

本文是前端学习笔记的第四篇,对应的是渡一教育的web前端开发JavaScript精英课js的第十三到十五课时,感觉难度还可以,闭包其实非常类似Java中的方法内部类,对比一下就很好理解了,主要就是记的知识点多,可能会忘

目录

闭包

立即执行函数


 

闭包

      当内部函数被保存到外部时,将会生成闭包,闭包会导致原有作用域链不释放,造成内存泄漏(占用着本应该释放的内存不释放,内存减少了,就叫内存泄漏)  

      闭包的作用:

  •   实现公有变量

                   应用:函数累加器

  •   可以做缓存(存储结构)  
  •   可以显式封装,属性私有化
  •   模块化开发,防止污染全局变量

    这里就以函数累加器为例子,简单的展示下闭包

   

<script>
        function outer() {
            var sum = 0;
            function count() {
                sum++;
                console.log(sum);
            }
            return count;
        }
        // count defined count.[[scope]] --> 0 : outerAO
        //                                   1 : GO
        // count doing   count.[[scope]] --> 0 : countAO
        //                                   1 : outerAO
        //                                   2 : GO
        var counter = outer();
        counter();  // 1
        counter();  // 2
        counter();  // 3
        counter();  // 4
</script>

   先定义了一个函数outer,其AO对象包含了一个num以及一个函数count,原本当outer函数被执行完,那么outer函数的AO对象也就完了,但是outer函数的返回值是其属性count函数,且这个属性的作用域链中保存了outer函数的执行期上下文对象,那么当outer函数结束后,outer回到定义状态,其作用域链中的AO对象的引用消失,但是此刻count函数的作用域链中还保存着outer函数的AO对象的引用,这样便形成了一个闭包,这样使得其属性私有化了,且把它们提升到了全局,但防止了对全局变量的污染

立即执行函数

   此类函数没有声明,一次执行过后便释放,适合做初始化工作

   立即执行函数的写法,主要有两种

  • (function ( ) { } ( ));           // W3C推荐写法
  • (function ( ) { } ) ( );        

   写在函数体的花括号后面的( )称为执行符号,这也是立即执行函数的最大特征

   立即执行函数很重要的一点,便是只有表达式才能被立即执行符号执行

   

<script>
    function test () {
        console.log("这是函数声明,不是表达式")  // 语法分析阶段错误
    } ();
</script>

  这样写是直接会报错的,因为这是函数声明,不是表达式,而只有表达式才能被立即执行符号执行

  回想以前学习过的函数定义方式,很容易回想到还有一种定义函数的方式:函数表达式 

<script>
    var test = function (a,b,c,d){
        console.log(a+b+c+d);   // 10
    }(1,2,3,4);
</script>

  刚刚最上面提到的小括号的立即执函数的写法,其实就是把函数声明转为了表达式,除此以外,立即执行函数也是可以有返回值

  

<script>
        var sum = (function (a,b,c,d) {
            console.log(a+b+c+d);
            return a+b+c+d;
        }(1,2,3,4));    // 记得这个分号一定要加
</script>

  接下来,就不得不提一提关于立即执行函数的一些面试题,先来看下面阿里的一道面试题,内容稍有改动,考点不变

  

<script>
    function test2() {
          console.log("好想进阿里巴巴");
    }(1,2,3,4);
</script>

  按我们上面的思路,这里应该是错的,因为只有表达式才可以被执行符号执行

  然而如果将这段代码扔到浏览器调试,就会发现并没有报错,当然控制台里也没有输出“好想进阿里巴巴”,这是因为浏览器会尽量让代码不报错,当浏览器看到逗号时就把(1,2,3,4)当做逗号表达式,相当于把(1,2,3,4)移到了下面

<script>
        function test3() {
            console.log("好想进阿里巴巴");
        };

        (1,2,3,4); // 不会报错

        test3();   // 好想进阿里巴巴
</script>

这里通过test3()来调用一下test3函数,成功输出期望的结果, 也侧面印证了test3不是立即执行函数,因为立即执行函数执行完后其引用便再也找不到了,无法再次执行   不愧是阿里。。这面试题的难度只能用鬼畜来形容了

最后再来看一个用立即执行函数来解决闭包诱发的一个问题的demo

<script>
        function test() {
            var arr = [];
            for(var i = 0;i<10;i++){
                arr[i] = function () {
                    // 感觉这里就是体现了JS的独特之处,函数也能作为一个对象存储
                    // 但也因此产生了一系列问题 比如每次赋予的因为都是函数,所以每次document.write(i + " ")中的i的值是动态获取的
                    // 并不是想象中的赋予一次就直接绑定了i是0123456789,而是最后一次函数终止时把i变成了10
                    // 那么在外部调用这些函数时,就会获取到这个已经变为10的i
                    document.write(i + " ");
                };
            }
            return arr;
        }

        var myArr = test();
        for(var j = 0;j<10;j++){
            myArr[j](); // 10 10 10 10 10 10 10 10 10 10
        }
</script>

这里本打算通过for循环保存输出对应好i的函数,从逻辑上来看貌似也是没有什么问题的,但最后输出的结果却很惊悚,10 10 10 10 10 10 10 10 10 10,具体原因注释那里也写的很清楚了,其实就是数组元素只是保存了当前这个函数的引用,最后调用这个函数时才最终确定 document.write(i + " "); 中的i的值,也就是说并不是每次循环到时就直接改变i的值,而是最后执行时才动态的获取的,而循环的最终结果是i变成了10,因此最后调用时就会输出10个10,

解决的方案也很简单,就是通过上面提到的立即执行函数

<script>
        function test() {
            var arr = [];
            for(var i = 0;i<10;i++){
                (function (j) {
                    arr[j] = function () {
                        document.write(j + " ");
                    }
                }(i));
            }
            return arr;
        }

        var myArr = test();
        for(var j = 0;j<10;j++){
            myArr[j]();   // 0 1 2 3 4 5 6 7 8 9
        }
</script>

  解决思路是通过立即执行函数的特点,每次循环时便立刻把当前的i发送到立即执行函数的形参处,通过设定另一个变量j把当前的i存储起来,那么此时函数便不再依赖于随时会改变的i了,因为即使i改变了,也不会改变保存了以前状态的i的值的j,因此此时就可以正常输出0 1 2 3 4 5 6 7 8 9了

猜你喜欢

转载自blog.csdn.net/Surrin1999/article/details/84070414