【CSON原创】 和大家一起分析js闭包中一个不常见的陷阱以及一个不常见的技巧

一个不常见的闭包陷阱:

var test1 = { name: 'Cson' };
var name = 'noName';
with (test1) {
function setName() {
name
= 'xiaoc';

}
setName();

}

alert(test1.name);
alert(name);

我们常常在资料中看到类似这样的描述:with打开对象闭包,在对象闭包里设置属性和方法等于为该对象添加属性以及方法。按这种思路理解,上面的结果应该分别是:'xiaoc'以及'noName'.

但是实验结果是:'Cson'以及'xiaoc'。(IE chrome)'xiaoc'以及'noName' (firefox)。

分析:

其实“在with对象闭包里设置属性和方法等于为该对象添加属性以及方法”这种说法并不完美,例外的情况就是当对象闭包里嵌套函数闭包的时候,这时除了firefox还是认同上面那句话之外,ie和chrome会用另一种方法处理。

我们知道javascript的有两个运行周期:解析周期以及执行周期。当处理上面过程的时候,firefox和IE chrome采用了不同的处理方式:

firefox认为:对象闭包内的声明,作用与对象闭包本身(也就是当作test1的属性处理),因此在执行周期的时候才把setName作为test1的一个方法属性创建,所以里面处理的name也就是test1的name属性了。

但是,ie和chrome认为:对象闭包内的声明,不应该作用与对象闭包。在js的解析周期,由于with还没起作用,因此它们把setName声明解析在全局闭包域里,这时就使setName和with成为了"同级",因此setName里设置的name就成为了全局的name变量。

解决方法:

综上所述,我们只需要把对象闭包内的函数声明改成赋值,就可以避免ie和chrome在js解析周期把setName解析在全局闭包里。

function setName() {
name
= 'xiaoc';

}

修改为

var setName=function() {
name
= 'xiaoc';

}

这样一来,“声明”变成“赋值”,所有浏览器引擎都会在js的执行周期处理该函数。

修改后firefox ie chrome输出均为:'xiaoc'以及'noName'

一个不常见的关于闭包的技巧:

大家应该对这个闭包的经典问题不陌生了:

<div id="a0">a0</div>
<div id="a1">a1</div>
<div id="a2">a2</div>
<div>
function test2() {
for (var i = 0; i < 3; i++) {
document.getElementById(
'a'+i).onclick= function() { alert(i); }
}
}
test2();

结果会是a0 a1 a2三个元素点击后alert的值均为3,因为闭包使它们使用了同一个i的值。

经典的解决方法是: 

function test2() {
for (var i = 0; i < 3; i++) {
(
function() {
var j = i;
document.getElementById(
'a' + i).onclick = function() { alert(j); }
})();
}
}
test2();

但是这样带来了个问题,只有外部的dom元素不解除引用,闭包使整个作用域链上的变量对象被保存在内存,代来了性能上的损耗,有没有更好的方法,不使用闭包解决该问题呢?



一个更好的方法如下: 

function test2() {
for (var i = 0; i < 3; i++) {

(document.getElementById(
'a' + i).onclick = function() { alert(arguments.callee.j); }).j = i;

}
}
test2();

很巧妙地通过为function设置自定义属性的方法,不用闭包,就完美解决了问题。



参考书籍:《javascript高级程序设计》《javascript语言精髓与编程实践》

欢迎转载,请标明出处:http://www.cnblogs.com/Cson/archive/2011/03/31/2001631.html

posted @ 2011-03-31 23:18  Cson  阅读(...)  评论(...编辑  收藏