闭包的概念与应用

什么是闭包?

做为一个 JavaScript 语言的开发者,提起闭包确定不会感到陌生,那么到底什么才是闭包哪?javascript

闭包不是什么新奇的概念,它早在高级语言开始发展的年代就产生了。闭包(Closure)是词法闭包的简称。对闭包的具体定义有不少种说法,这些说法大致能够分为两类:java

  • 一种说法认为闭包是符合必定条件的函数。认为闭包是在其词法上下文中引用了自由变量(自由变量是指局部变量之外的变量)的函数。
  • 另外一种说法认为闭包是函数和与其相关的引用环境组合而成的实体。认为闭包是在实现深约束时,须要建立一个能显示表示引用环境的东西,并将它与相关的子程序捆绑在一块儿,这样捆绑起来的总体被称为闭包。

这两种定义在某种意义上是对立的,一个认为闭包是函数,另外一个认为闭包是函数和引用环境组成的总体。很明显第二种说法更确切一些,闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就肯定了,不会在执行时发生变化,因此一个函数只有一个实例。闭包在运行时能够有多个实例,不一样的引用环境和相同的函数组合能够产生不一样的实例。所谓引用环境是指在程序执行中的某个点全部处于活跃状态的约束组成的集合。其中的约束是指一个变量的名字和其所表明的对象之间的联系。linux

JavaScript 闭包的本质

在支持嵌套做用域的语言中,有时不能简单直接的肯定函数的引用环境。这样的语言通常具备这样的特性:设计模式

  • 函数是一等公民,即函数能够做为一个函数的返回值或参数,还能够做为一个变量的值
  • 函数能够嵌套定义,即在一个函数内部能够定义另外一个函数。

JavaScript 闭包的源自两点,词法做用域和函数当作值传递数组

做用域是查找变量时的一些规则。词法做用域就是定义在词法阶段的做用域。或者换句话说,词法做用域是由你书写代码时将变量和块做用域写在哪里来决定的。按照代码书写时的样子,内部函数能够顺着做用域链一层一层地查找、访问函数外的变量,或者咱们叫它自由变量。浏览器

函数当作值传递,也就是上面所说的函数是一等公民。函数内部的自由变量是在外层函数执行时建立的,外层函数执行完之后,这些变量理应被销毁,可是若是将内层函数做为返回值返回,这些自由变量就被保存了下来。并且没法访问,必须经过内层函数来访问。原本执行过程和词法做用域是封闭的,将内层函数做为返回值返回就提供了一种访问自由变量的方式。闭包

一个函数如何能封闭外部状态哪?当外部状态的scope失效的时候,还有一份留在内部状态里面。在执行过程当中,返回函数,或者将函数得以保留下来,而且函数中有自由变量就会造成闭包。一个函数中没有自由变量时,引用环境不会发生变化函数

闭包的应用

知道了什么是闭包,也理解了闭包的本质,下面能够了解下闭包的几种应用,或许你在平常的开发中已经用到很多了。ui

封装私有变量,存储计算的值

// 将计算的结果保存在 sum 中
function add(init) {
  var sum = init;
  return function getSum(number) {
    sum += number;
    return sum;
  }
}
复制代码

延迟计算

// 延迟计算
function add(init) {
  var sum = init;
  var args = [];
  return function getSum() {
    // 当参数到达必定的数量时再进行运算
    args = args.concat(Array.from(arguments));
    if(args.length > 5) {
      for(let i = 0; i < args.length; i++) {
        sum += args[i];
      }
      return sum;
    }
  }
}
复制代码

延续局部变量的寿命

img 对象常常用于进行数据上报,可是经过查询后台的记录能够得知,由于一些低版本的浏览器的实现可能存在 bug,在这些浏览器下使用 report 函数进行数据上报会丢失 30% 左右的数据,也就是说,report 函数并非每一次都发起了 HTTP 请求。丢失数据的缘由是 img 是 report 函数中的局部变量,当 report 函数调用结束后, img 局部变量随即被销毁,而此时或许还没来得及发出 HTTP 请求,因此这次请求就会丢失掉。spa

// 这种方法会丢失 30% 左右的数据
var report = function (src) {
  var img = new Image();
  img.src = src;
};

// 把 img 变量封装起来,就能够解决请求丢失的问题
var report = (function(){
  var imgs = [];
  return function(src) {
    var img = new Image();
    imgs.push(img);
    img.src = src;
  }
})();
复制代码

私有数据和应用程序接口

有时,你想强制程序与数据的交互方式,以便保护其完整性。经过是使用闭包,彻底能够作到这一点。建立此类接口的一种常见方法就是从函数返回对象。这时,定义在原函数中的数据只能由返回对象上定义的方法访问,下面是一个例子:

function makeCalendar(name) {
  var calendar = {
    owner: name,
    events: [],
  };
  
  return {
    addEvent: function(event, dateString) {
      var eventInfo = {
        event: event,
        date: new Date(dateString),
      };
      calendar.events.push(eventInfo);
      calendar.events.sort(function(a, b) {
        return a.date - b.date;
      });
    },
    
    listEvents: function() {
      if (calendar.events.length > 0) {
        console.log(calendar.owner + "'s events are: ");
        
        calendar.events.forEach(function(eventInfo) {
          var dateStr = eventInfo.date.toLocaleDateString();
          var description = dateStr + ": " + eventInfo.event;
          
          console.log(description);
        });
      } else {
        console.log(calendar.owner + " has no events.");
      }
    },
  };
}
复制代码

闭包与内存管理

局部变量原本应该在函数退出的时候被解除引用,但若是局部变量被封闭在闭包造成的环境中,那么这个局部变量就会一直存在。在这个意义上看,闭包的确会使一些数据没法被及时销毁。使用闭包的一部分缘由是咱们选择主动把一些变量封闭在闭包中,由于可能在之后还须要使用这些变量,把这些变量放在闭包中和放在全局做用域,对内存方面的影响是一致的。若是在未来须要回收这些变量的时候,能够手动把这些变量设置为 null。

参考内容

相关文章
相关标签/搜索