0%

Javascript 闭包

闭包(Closure)的官方解释晦涩难懂,他是这么说的:

「闭包」,是指拥有多个变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

不拿代码举例的话,谁知道这说的是个什么玩意儿?直到我看到了如下说明:

闭包是个函数,而它「记住了周围发生了什么」。表现为由「一个函数」体中定义了「另个函数」。

这样说似乎还是不大容易理解,于是 MDN 写了这个例子(demo):

function init() {
var name = “Mozilla”;
function displayName() {
alert(name);
}
displayName();
}
init();

然后我们发现其实改成这样也是可以的(demo):

function init() {
var name = “Mozilla”;
return function() {
alert(name);
}
}
var func = init();
func();

这个时候 func 是一个闭包,由返回的匿名函数函数和闭包创建时存在的 “Mozilla” 字符串形成。然后我们再来加点料(demo):

function init(product) {
var name = product;
return function(value) {
alert(name +’ makes ‘+ value);
}
}
var mozilla = init(‘mozilla’),
google = init(‘google’),
apple = init(‘apple’);
mozilla(‘firefox’);
google(‘chrome’);
apple(‘safari’);

上述例子中会依次弹出「mozilla makes firefox」、「google makes chrome」、「apple makes safari」,new 了一个 class 的既视感有木有? 使用闭包还可以模拟私有属性和方法(demo):

var println = function(d){
var el = document.createElement(‘p’), text = document.createTextNode(d);
el.appendChild(text);
document.body.appendChild(el);
},
outter = (function(){
var private = ‘私有变量’;
return{
get: function(){
println(private);
},
set: function(d){
private = d;
println(‘成功设置私有变量为’+ d);
}
}
}());
outter.get();
println(outter.private);
outter.set(‘hola!’);
outter.get();

这样做了以后就只有使用 setter 和 getter 才可以访问自己模拟的私有方法或属性了。 在循环中创建闭包时容易发生多个闭包共用最后一次循环时产生的数据这样的错误,MDN 给出的方案是使用更多的闭包(错误正确):

/* 绑定的参数错误 */
for (var i = 0; i < 5; i++) {
var p = document.getElementsByTagName(‘p’)[i];
p.onclick = function(){
alert(i);
};
}

/* 修改后 */
for (var i = 0; i < 5; i++) {
(function(j){
var p = document.getElementsByTagName(‘p’)[j];
p.onclick = function(){
alert(j);
};
}(i));
}

更复杂的例子 不过说了很多,其实最后发现「闭包」着实是一个比较蛋疼的技术,如果你只是常规的页面交互使用概率还是比较小的,关于此,MDN 如是说

如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。 例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用,方法都会被重新赋值一次(也就是说,为每一个对象的创建)。

参考资料: Secrets of JavaScript Closures 闭包 - MDN