闭包

2019-09-25  本文已影响0人  zooeydotmango

闭包是一种特殊的对象,它由两部分组成。执行上下文(代号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循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

上一篇下一篇

猜你喜欢

热点阅读