闭包

作者: zooeydotmango | 来源:发表于2019-09-25 01:39 被阅读0次

    闭包是一种特殊的对象,它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。
    当B执行时,如果访问了A中变量对象的值(VO),那么就产生了闭包。

    大多数人以B的名字代指在这里产生的闭包,chrome则以A的名字代指闭包。

    本文将使用chrome的方式,将A的函数名代指这里产生的闭包。

    // demo01
    function foo() {
        var a = 20;
        var b = 30;
    
        function bar() {
            return a + b;
        }
    
        return bar;
    }
    
    var bar = foo();
    bar();
    

    在这个例子中,首先有执行上下文foo,foo中定义了函数bar,而通过return bar的方式让bar得以执行。当bar执行时,访问了foo内部的变量a,b。因此产生了闭包。

    我们已经知道JS中一个值在内存中失去引用时,垃圾回收机制会根据特殊算法找到它,并将其回收,释放内存。

    而函数的执行上下文在执行结束后,生命周期结束,就会失去引用。其空间很快就会被回收。而闭包会阻止这一过程。

    var fn = null;
    function foo() {
        var a = 2;
        function innnerFoo() {
            console.log(a);
        }
        fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
    }
    
    function bar() {
        fn(); // 此处的保留的innerFoo的引用
    }
    
    foo();
    bar(); // 2
    

    比如上例中,foo执行完后,按照常理,应该被垃圾回收。但是通过fn = innerFoo,函数innerFoo的引用被保存了下来。这个行为导致foo的VO也被保存了下来。于是fn在bar内运行的时候,依然可以访问到变量a的值。

    这种情况,我们就可以称foo为闭包

    下图展示了闭包foo的作用域链


    闭包与作用域链

    闭包的应用场景

    1. 函数柯里化-只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数
    2. 模块
    (function () {
        var a = 10;
        var b = 20;
    
        function add(num1, num2) {
            var num1 = !!num1 ? num1 : a;
            var num2 = !!num2 ? num2 : b;
    
            return num1 + num2;
        }
    
        window.add = add;
    })();
    
    add(10, 20);
    

    在这个例子中,使用立即执行函数创建了一个模块。add是模块对外暴露的一个公共方法,而a,b是私有变量。

    闭包的一些练习

    var fnArr = [];
    for (var i = 0; i < 10; i ++) {
      fnArr[i] =  function(){
        return i
      };
    }
    console.log( fnArr[3]() ) // 10
    

    因为var i定义了全局变量,在执行fnArr[3]的时候i已经是10,可以应用闭包对函数进行改写

    //方法1
    var fnArr = []
    for (var i = 0; i < 10; i ++) {
      fnArr[i] =  (function(j){
        return function(){
          return j
        } 
      })(i)
    }
    console.log( fnArr[3]() ) // 3
    //方法2
    var fnArr = []
    for (var i = 0; i < 10; i ++) {
      (function(i){
        fnArr[i] =  function(){
          return i
        } 
      })(i)
    }
    console.log( fnArr[3]() ) // 3
    //这两个方法的原理类似,对fnArr[i]的赋值立即执行,产生了10个闭包
    //匿名函数传入的参数本该在函数结束时被垃圾回收,但是因为fnArr的调用一直保存
    
    //方法3
    var fnArr = []
    for (let i = 0; i < 10; i ++) {
      fnArr[i] =  function(){
        return i
      } 
    }
    console.log( fnArr[3]() ) // 3
    //原理是es6的let命令
    //let所声明的变量,只在let命令所在的代码块内有效。
    

    上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是3。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

    另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

    相关文章

      网友评论

          本文标题:闭包

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