我们来看看闭包的用途。 事实上,我们可以利用闭包做很多事情。 例如,模拟面向对象的编码风格; 以更高尚、更简洁的方式表达代码; 并在各个方面提高代码的执行效率。
1 匿名自执行函数
我们知道,所有的变量javascript闭包的作用,如果不添加var关键字,都会默认添加到全局对象的属性中。 将此类临时变量添加到全局对象有很多好处。
例如:其他函数可能会滥用此类变量; 导致全局对象太大,影响访问速度(因为变量的值需要从原型链中遍历)。
除了每次使用变量时都使用var关键字外,我们在实际情况中经常会遇到这样的情况,即有些函数只需要执行一次,而其内部变量不需要维护。
例如,对于 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);
我们创建一个匿名函数并立即执行它。 由于很难从外部引用其内部变量,
因此,他们在被执行后很快就会被释放。 关键是这些机制不会污染全局对象。
2 缓存
让我们看另一个例子。 假设我们有一个处理需要很长时间的函数对象。 每次调用都会花费很长时间。
然后我们需要存储估计值。 调用该函数时,首先在缓存中查找。 如果找不到javascript闭包的作用,则估计它。
然后更新缓存并返回值。 如果找到,则直接返回找到的值。 闭包正是这样做的,因为它们不释放外部引用。
这样就可以保留函数内部的值。
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")时,
我们可以从缓存中检索对象,而无需创建新的搜索框对象。
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
4 闭包的另一个重要用途是在面向对象中实现对象
传统的对象语言都提供类模板机制。
这样,不同的对象(类的实例)就有独立的成员和状态,互不干扰。 虽然 JavaScript 中没有类似类的机制,但通过使用闭包,
我们可以模拟这样一个机制。 我们以上面的例子为例:
function Person(){
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
};
var john = Person();
print(john.getName());
john.setName("john");
print(john.getName());
var jack = Person();
print(jack.getName());
jack.setName("jack");
print(jack.getName());
从这段代码可以看出,john和jack都可以称为Person类的实例,因为两个实例对name成员的访问是独立的,互不影响。