闭包:有权访问另一个函数作用域中的变量的函数。
创建闭包的常用方式:就是在一个函数内部创建另外一个函数。
1.快速开始
这样所确实不好理解,现在举一个简单的例子,快速入门。
function a(){
var i=0;
function b(){
alert(++i);
}
return b;
}
var c=a();
c();//1
这样在执行完var c=a( )后,变量c实际上是指向了函数b,再执行c( )后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,为什么?因为函数a外的变量c引用了函数a内的函数b,就是说:
当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。
这个时候我们仍然不太清楚怎么回事。现在我们先不看原理,直接过来看看它的作用。
2.闭包的作用
一句话:闭包的活动对象(变量)会常驻内存。
a执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量
3.闭包的原理
这部分是最复杂的,先举一个例子来提出来几个概念。
参考 Javascript高级程序设计(第三版)
function compare(value1, value2){
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);
当调用compare()时,会创建一个包含arguments、value1 和value2 的活动对象。全局执行环境的变量对象(包含result和compare)在compare()执行环境的作用域链中则处于第二位。
概念:
执行环境:当调用compare()函数的时候,会为函数创建一个执行环境,通过复制函数的[[Scope]]属性中的对象建立作用域链。
作用域链:作用域链本质上是一个指向变量对象的指针列表,它只
引用但不实际包含变量对象。包含:全局变量的对象和compare()的活动对象。
一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。
但是,闭包又有所不同:
function createComparisonFunction(propertyName) {
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}
当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中(匿名函数的作用域链仍然在引用这个活动对象);直到匿名函数被销毁后,createComparisonFunction()的活动对象才会
被销毁
//创建函数
var compareNames = createComparisonFunction("name");
//调用函数
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });
//解除对匿名函数的引用(以便释放内存)
compareNames = null;
4.闭包的用途
4.1 匿名自执行函数
例子:UI初始化,只需要执行一次,其内部变量无需维护
var datamodel = {
table : [],
tree : {}
};
(function(dm){
for(var i = 0; i < dm.table.rows; i++){
var row = dm.table.rows[i];
for(var j = 0; j < row.cells; i++){
drawCell(i, j);
}
}
//build dm.tree
})(datamodel);
4.2 缓存
var CachedSearchBox = (function(){
var cache = {},
count = [];
return {
attachSearchBox : function(dsid){
if(dsid in cache){//如果结果在缓存中
return cache[dsid];//直接返回缓存中的对象
}
var fsb = new uikit.webctrl.SearchBox(dsid);//新建
cache[dsid] = fsb;//更新缓存
if(count.length > 100){//保正缓存的大小<=100
delete cache[count.shift()];
}
return fsb;
},
clearSearchBox : function(dsid){
if(dsid in cache){
cache[dsid].clearSelection();
}
}
};
})();
CachedSearchBox.attachSearchBox("input1");
这样,当我们第二次调用CachedSearchBox.attachSerachBox(“input1”)的时候,
我们就可以从缓存中取道该对象,而不用再去创建一个新的searchbox对象。
4.3 实现封装
var person = function(){
//变量作用域为函数内部,外部无法访问
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}();
print(person.name);//直接访问,结果为undefined
print(person.getName());
person.setName("abruzzi");
print(person.getName());
得到结果如下:
undefined
default
abruzzi