美文网首页
什么是闭包?

什么是闭包?

作者: 小碗碗碗碗 | 来源:发表于2020-06-29 15:47 被阅读0次
    当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
                                                      —— 你不知道的JavaScript(上卷)
    

    一、闭包的定义

    1、闭包的构成
    首先,闭包由两部分构成:函数创建该函数的环境,环境由闭包创建时在作用域中的任何局部变量组成。

    JS的变量作用域
    变量的作用域有两种:全局变量和局部变量。
    函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。

    那么,如何从外部读取函数内部的局部变量?在下面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。
    这就是Javascript语言特有的 链式作用域 结构(chain scope):子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
    既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,就可以在f1外部读取f1的内部变量。

    function f1(){
      var n=999;
      function f2(){
        alert(n); 
      }
    }
    var test = f1();// 外部函数调用之后其变量对象n本应该被销毁
    test();// alert 999,但闭包阻止了它们的销毁,所以仍然可以访问外部函数的变量对象
    

    上面代码中的f2函数,就是闭包。
    在JS中,只有函数内部的成员才能读取局部变量。所以在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

    二、闭包的特性

    在JavaScript中,外部函数调用之后其变量对象本应该被销毁,但闭包阻止了它们的销毁,所以仍然可以访问外部函数的变量对象。

    function addCalculator (x) {
        return function (y) {
            return x + y;
        }
    }
    
    var add1 = addCalculator(1);
    console.log(add1(1)); //2
    
    add1 = null;// 释放对闭包的引用
    console.log(add1(1)); //Uncaught TypeError: add1 is not a function
    

    通常情况下,函数的作用域及其所有变量都会在函数执行结束后被销毁。
    但如果创建了一个闭包的话,这个函数的作用域就会一直保存到闭包不存在为止。

    闭包的特性一般为以下几点:
    1、闭包一般为外部函数嵌套的内部函数、以及创建该内部函数的环境组成的;
    2、闭包可以引用外部函数的参数和变量,即可以访问外部的环境;
    3、闭包未被释放回收的情况下,若闭包中引用了外部函数的参数和变量,即使外部环境已经被释放回收,但是外部函数的参数和变量依然不会被垃圾回收机制回收。

    三、闭包的作用

    1、通过闭包来模拟私有方法
    私有方法有利于限制对代码的访问,而且可以避免非核心的方法干扰代码的公共接口,减少全局污染。
    下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量。这个方式也称为模块模式(module pattern)

    var calculator = (function(){
        var a = 1;
        function addCalculator(val){
            a += val
        }
        return {
            add1:function() {
                addCalculator(1);
            },
            add2:function() {
                addCalculator(2);
            },
            result:function() {
                return a
            }
        }
    })();
    
    console.log(calculator.result());  // 1
    calculator.add1();
    console.log(calculator.result());  // 2
    calculator.add2();
    console.log(calculator.result());  // 4
    

    在之前的示例中,每个闭包都有它自己的词法环境。而这次例子中只创建了一个词法环境,为三个函数所共享:calculator.add1,calculator.add2和 calculator.result。

    该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 a的变量和名为 addCalculator的函数,这两项都无法在这个匿名函数外部直接访问,必须通过匿名函数返回的三个公共函数访问。

    这三个公共函数是共享同一个环境的闭包,它们都可以访问 a变量和 addCalculator函数。

    四、闭包的优缺点

    1、优点

    • 保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
    • 在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
    • 匿名立即执行函数可以减少内存消耗

    2、缺点

    • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以滥用闭包的话会造成网页的性能问题,在IE(IE9)之前可能导致内存泄露。
      解决方法:在退出函数之前,将不使用的局部变量全部删除(例如手动赋值为null)
    • 由于闭包涉及跨域访问,所以会导致性能损失。
      解决方法:可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。

    引申:为何闭包的不当使用会在IE(IE9)之前可能导致内存泄漏,即无法回收变量的问题?
    因为IE(IE9)之前的JavaScript引擎使用的垃圾回收算法是引用计数法,对于循环引用将会导致GC(Garbage Collection)无法回收垃圾。
    注:在后面的章节中,我会找机会继续讨论什么是GC(Garbage Collection)

    PS:更新来啦O(∩_∩)O,下一篇文章讲到了GC(Garbage Collection):JS的垃圾回收机制

    相关文章

      网友评论

          本文标题:什么是闭包?

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