做为一个 JavaScript 语言的开发者,提起闭包确定不会感到陌生,那么到底什么才是闭包哪?javascript
闭包不是什么新奇的概念,它早在高级语言开始发展的年代就产生了。闭包(Closure)是词法闭包的简称。对闭包的具体定义有不少种说法,这些说法大致能够分为两类:java
这两种定义在某种意义上是对立的,一个认为闭包是函数,另外一个认为闭包是函数和引用环境组成的总体。很明显第二种说法更确切一些,闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就肯定了,不会在执行时发生变化,因此一个函数只有一个实例。闭包在运行时能够有多个实例,不一样的引用环境和相同的函数组合能够产生不一样的实例。所谓引用环境是指在程序执行中的某个点全部处于活跃状态的约束组成的集合。其中的约束是指一个变量的名字和其所表明的对象之间的联系。linux
在支持嵌套做用域的语言中,有时不能简单直接的肯定函数的引用环境。这样的语言通常具备这样的特性:设计模式
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。