One Day One Tip 之 闭包
时间:2016-08-30 13:39:33
作者:zhongxia
总结:
概念:
闭包:能够读取其他函数内部变量的函数,在JavaScript中,一个函数return它内部的一个函数。
原理:通过引用变量从而阻止该变量被垃圾回收的机制
优:
- 封装私有属性和私有方法,加强封装性,可以达到对变量的保护作用。
- 更好的组织代码,比如模块化
缺:
- 增加了内存的消耗,并且在某些浏览器下,由于垃圾回收机制不同,有可能导致内存溢出
- 增加复杂度
- 由于闭包内部变量优先级高于外部变量,所以多查找作用域链中的一个层次,就会在一定程度上影响查找速度。
零、所需知识
要理解闭包,首先必须理解 JavaScript 特殊的变量作用域。
变量的作用域无非就是两种: 全局变量 和 局部变量
JavaScript语言的特殊之处,就在于 函数内部可以直接读取全局变量。
不使用 var 声明的变量,则为全局变量。 b = 100;
function fn(){
var a = b = 1;
// ==> var a = window.b = 1; // a 是局部变量 b 是全局变量
}
fn();
console.log("b = ",b); // 1
console.log("a = ",a); //VM783:1 Uncaught ReferenceError: a is not defined
函数外部无法访问局部变量。 因此在外部,访问 a 变量 报错。 而 b 变量是 全局变量,因此可以访问到。
一、闭包是什么
闭包是概念?
闭包是指某种程序语言中的代码块允许一级函数存在并且在一级函数中所定义的自由变量能不被释放,直到一级函数被释放前,一级函数外也能应用这些未释放的自由变量。
我的理解就是:闭包就是能够读取其他函数内部变量的函数
由于在JavaScript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单的理解成 『定义在一个函数内部的函数』
本质上: 闭包就是将函数内部和外部链接起来的一座桥梁。
//eg: example
function a(x){
var tmp = 10;
return function(y){
return (x+y)+(++tmp);
}
}
var b = a(10);
b(5); //26, 每执行一次 tmp 加 1
因为 a() 执行后,返回 的 方法 b, 内部引用了 tmp 变量, 导致 tmp 变量的标记+1, 垃圾回收机制就不会清除 tmp这个变量。
然后外部就可以继续访问到 tmp 变量。
二、闭包的作用
- 读取函数内部的变量
- 让内部的变量始终保持在内存中
- 设计私有方法和变量【封装框架的时候更明显,典型的如Jquery】
var jQuery = (function(){
var jQuery = function(){
//TODO
}
return (window.$ = window.jQuery = jQuery);
});
三、闭包的优缺点
优点
- 延长作用域链。
- 更好的组织代码,比如模块化,异步代码转同步等。
- 加强封装性,可以打到对变量的保护(第二点的加强)
- 处理异步造成的变量不能即时传递的问题
缺点
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除
- 增加内存的消耗
- IE浏览器上 因为回收机制,有内存溢出的风险
- 增加了代码复杂度
四、闭包用法实战
1. 对fun的计算方式进行定制
实际使用的时候,闭包可以创建出非常优雅的设计,允许对funarg上定义的多种计算方式进行定制。如下就是数组排序的例子,它接受一个排序条件函数作为参数:
[1, 2, 3].sort(function (a, b) {
... // 排序条件
});
同样的例子还有,数组的map方法是根据函数中定义的条件将原数组映射到一个新的数组中:
[1, 2, 3].map(function (element) {
return element * 2;
}); // [2, 4, 6]
2. 函数式参数
使用函数式参数,可以很方便的实现一个搜索方法,并且可以支持无限制的搜索条件:
someCollection.find(function (element) {
return element.someProperty == 'searchCondition';
});
还有应用函数,比如常见的forEach方法,将函数应用到每个数组元素:
[1, 2, 3].forEach(function (element) {
if (element % 2 != 0) {
alert(element);
}
}); // 1, 3
顺便提下,函数对象的 apply 和 call方法,在函数式编程中也可以用作应用函数。 apply和call已经在讨论“this”的时候介绍过了;这里,我们将它们看作是应用函数 —— 应用到参数中的函数(在apply中是参数列表,在call中是独立的参数):
(function () {
alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);
3. 延迟调用
闭包还有另外一个非常重要的应用 —— 延迟调用:
var a = 10;
setTimeout(function () {
alert(a); // 10, after one second
}, 1000);
4. 回调函数
//...
var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
// 当数据就绪的时候,才会调用;
// 这里,不论是在哪个上下文中创建
// 此时变量“x”的值已经存在了
alert(x); // 10
};
//...
5. 创建封装的作用域来隐藏辅助对象:
var foo = {};
// 初始化
(function (object) {
var x = 10;
object.getX = function _getX() {
return x;
};
})(foo);
alert(foo.getX()); // 获得闭包 "x" – 10