javascript闭包详解
Table of Contents
闭包的两个知识点:
- 变量搜索方向:在javascript中,函数可以创造函数作用域。在函数作用域里面可以看到外面的变量,而函数的外面无法访问到函数里面的变量。因此,在作用域里面的变量搜索方向是:自内向外。比如,下面这段代码就验证了这个搜索方向:
var a = 1;
var func1 = function(){
var b = 2;
var func2 = function(){
var c = 3;
console.log(b); // 输出:2
console.log(a); // 输出:1
}
func2();
console.log(c); // 输出:Uncaught ReferenceError: c is not defined
};
func1();
- 变量的生存的周期:全局变量的生存周期是永久的,局部变量随着函数调用的结束而销毁。
// 例一
var func = function(){
var a = 1; // 退出函数后局部变量a将被销毁
console.log(a);
};
func();
// 例二
var func = function(){
var a = 1;
return function(){
a++;
console.log(a);
}
}
var f = func();
f(); // 输出:2
f(); // 输出:3
f(); // 输出:4
f(); // 输出:5
闭包举例
<!DOCTYPE html>
<html>
<body>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<script>
var nodes = document.getElementsByTagName('div');
for(var i=0, len = nodes.length; i < len; i++){
(function(i){
nodes[i].onclick = function(){
alert(i);
};
})(i);
}
</script>
</body>
</html>
var Type = {};
for(var i=0, type; type=['String', 'Array', 'Number'][i++]; ){
(function(type){
Type['is' + type] = function(obj){
return Object.prototype.toString.call(obj) === '[object '+ type +']';
}
})(type);
}
console.log(Type.isArray([])); // 输出: true
console.log(Type.isString("str")); // 输出: true
闭包的作用
- 封装变量:我们通过一个计算乘积的简单函数来介绍,这个计算函数加入缓存机制,来提高函数的而运行性能,然而cache处于全局作用域下面,但是只被mult函数使用。
var cache = {};
var mult = function(){
var args = Array.prototype.join.call(arguments, ',');
if(cache[args]){
return cache[args];
}
var a = 1;
for(var i = 0, l = arguments.length; i < l; i++){
a = a * arguments[i];
}
return cache[args] = a;
};
console.log(mult(1, 2, 3)); // 输出:6
console.log(mult(1, 2, 3)); // 输出:6
为了避免这个变量在其他地方被不小心修改而引发错误,我们把cache加入到函数作用域下面,用闭包封装起来,具体代码如下:
var mult = (function(){
var cache = {};
return function(){
var args = Array.prototype.join.call(arguments, ',');
if(args in cache){
return cache[args];
}
var a = 1;
for(var i = 0, l = arguments.length; i < l; i++){
a = a * arguments[i];
}
return cache[args] = a;
}
})();
mult(1, 2, 3); // 输出:6
从上面我们可以看出,计算乘积的部分可以独立出来,提炼函数是代码重构的一种常见技巧,独立出来的小函数由于不需要在其他位置使用,我们可以使用闭包封装起来,代码如下:
var mult = (function () {
var cache = {};
var caculate = function () { // 封闭caculate函数
var a = 1;
for (var i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i];
}
return a;
}
return function () {
var args = Array.prototype.join.call(arguments, ',');
if (args in cache) {
return cache[args];
}
return cache[args] = caculate.apply(null, arguments);
}
})();
console.log(mult(1, 2, 3));
console.log(mult(1, 2, 3));
- 延续局部变量的寿命: 我们通过一个数据上报的例子来说明这种情况。
var report = function(src){
var img = new Image();
img.src = src;
};
report('http://xxx.com/getUserInfo');
由于浏览器的兼容性问题,在report函数进行上报之前就会丢失30%左右的数据,由于report函数的http请求并不是每次都有效的。导致这个问题的根本原因在于img是局部变量,img变量随着report函数的结束而销毁。通过闭包可以解决这个bug。代码如下:
var report = (function(src){
var imgs = [];
return function(src){
var img = new Image(img);
imgs.push(img);
img.src = src;
}
})();
report('http://xxx.com/getUserInfo');
上面代码中的imgs依靠report这个全局变量而存在,而之前的report是一个函数变量,里面的变量随着函数结束而结束。
本篇博客参考自JavaScript设计模式与开发实践,[p35-p40]