

1、概念
各种专业文献的闭包定义都非常抽象,我的理解是: 闭包就是能够读取其他函数内部变量的函数,简单理解就是”定义在一个函数内部的函数”
2、特点
闭包有两个很明显的特点:
1. 函数拥有的外部变量的引用,在函数返回时,该变量仍然处于活跃状态。
2. 闭包作为一个函数返回时,其执行上下文环境不会被销毁,仍处于执行上下文环境中。
在JavaScript中存在一种内部函数,即函数声明和函数表达式可以位于另一个函数的函数体内,在内部函数中可以访问外部函数声明的变量,当这个内部函数在包含它们的外部函数之外被调用时,就会形成闭包。
function fn()
{ var max = 10;
return function bar(x){
if(x>max){
console.log(x);
}
};
}
var f1 = fn();
f1(11);
3、 闭包的用途
1. 结果缓存
在开发过程中,我们可能会遇到这样的场景,假如有一个处理很耗时的函数对象,每次用都会消耗很长时间。我们可以将其处理结果在内存中缓存起来。这样在执行代码时,如果内存中有,则直接返回,如果内存中没有,则调用函数进行计算,更新缓存并返回结果。因为闭包不会释放外部变量的引 用,所以能将外部变量值缓存在内存中。
var cacheBox = (function () {
//缓存的容器
var cache = {};
return {
searchBox: function (id) {
//如果在内存中,则直接返回
if (id in cache) {
return '查找的结果为:' + cache[id];
}
//经过一段很耗时的dealFn()函数处理
var result = dealFn(id); //更新缓存的结果
cache[id] = result;
//返回计算的结果
return '查找的结果为:' + result;
},
};
})(); //处理很耗时的函数
function dealFn(id) {
console.log('这是一段很耗时的数据库查询操作');
return id;
} //两次调用cacheBox()函数
console.log(cacheBox.searchBox(1));
console.log(cacheBox.searchBox(1));
2.封装
在JavaScript中提倡的模块化思想是希望将具有一定特征的属性封装到一起,只需要对外暴露对应的函数,并不关心内部实现的逻辑。 例如:我们可以借助数组实现一个栈,只对外暴露出表示入栈和出栈的push()函数和pop函数,以及表示栈长度的size()函数。
var stack = (function () {
//实现数组模仿栈的实现
var arr = []; //栈
return {
push: function (value) {
arr.push(value);
},
pop: function () {
return arr.pop();
},
size: function () {
return arr.length;
},
};
})();
stack.push('abc');
stack.push('def');
console.log(stack.size());
stack.pop();
console.log(stack.size());
3.定时器问题
定时器setTimeout()函数和for循环在一起使用,总会出现一些意想不到的结果,我们来看一下:
var arr = ['one', 'two', 'three'];
for (var i = 0; i < arr.length; i++) {
setTimeout(function () {
console.log(arr[i]);
}, i * 1000);
}
我们想要间隔一秒输出数组中的数据,但是运行过后却会间隔一秒输出undefined,这是为什么 呢?因为for循环的结束条件i变成3所以arr[3]找不到,结果就是三次undefined。 通过闭包可以解决这个问题:
var arr = ['one', 'two', 'three'];
for (var i = 0; i < arr.length; i++) {
(function (time) {
setTimeout(function () {
console.log(arr[time]);
}, time * 1000);
})(i);
}
4.作用域链问题
闭包往往会设计到作用域链的问题,尤其是包含this属性时。
var name = 'outher';
var obj = {
name: 'inner',
method: function () {
return function () {
return this.name;
};
},
};
console.log(obj.method()());
在调用obj.method()函数时,会返回一个匿名函数,而该匿名函数中返回的是this.name,因为引用到了this属性,在匿名函数中,this相当于一个外部变量,所以会形成一个闭包。 在JavaScript中,this指向的永远是函数的调用实体,而匿名函数的实体是全局对象window,因此 输出全局变量name的值是outer。 如果想要输出obj对象自身的name属性,就要改变this的指向,将其指向obj对象本身。
var name = 'outher';
var obj = {
name: 'inner',
method: function () {
//用_this保存obj中的this
var _this = this;
return function () {
return _this.name;
};
},
};
console.log(obj.method()());
4、闭包总结
闭包如果使用合理,在一定程度上能够提高代码的执行效率,如果使用不合理,则会造成内存浪 费,性能下降,接下来我们总结一下闭包的优缺点。
闭包的优点
1. 保护函数内变量的安全,实现封装,防止变量流入其他环境发生命名冲突,造成环境污染。
2. 在适当的时候,可以在内存中维护变量并缓存,提高执行效率。
闭包的缺点
1. 消耗内存,通过来说,函数的活动对象会随着执行的上下文环境一起被销毁,但是由于闭包引用的 是外部函数的活动对象,因此这个活动对象无法被销毁,这意味着闭包比普通函数要消耗更多的内 存。
2. 内存泄漏,在IE9之前,如果闭包的作用域链中存在DOM对象,则意味着该DOM对象无法被销毁, 造成内存泄漏。