日期:2014-05-16  浏览次数:20513 次

javascript中循环添加事件时的闭包问题解决

这是一个经典问题,只不过有一段时间不写纯js了,最近老是掉进以前跌过的坑,这次花了半天时间爬出来,所以记录一下。

?

昨天写js代码在给自定义的div注册onclick事件时发现得到的值始终是循环里的最后一个层里的值。

最早的代码:

?

for(var i in array){
	var rowDiv = document.createElement("div");
	rowDiv.innerText = array[i]["name"];
	rowDiv.onclick = function(){//注册点击事件
		pNode = this.parentNode;
		pNode.previousSibling.value = this.innerText;//将当前div的值赋给输入框
		if(onclickCallback){//传入了回调函数则执行回调
			onclickCallback(array[i]);
		}
	};
}

?

为了不干扰阅读的视线,将与这个问题无关的代码直接裁掉:

?

for(var i in array){
	var rowDiv = document.createElement("div");
	rowDiv.innerText =array[i];
	rowDiv.onclick = function(){//注册点击事件
		alert(array[i]);
	};
}

?

这里发现点击一个div之后alert出来的始终是最后一次循环的值。这个现象立马想起来这是一个经典的闭包问题,在执行onclick的事件时会向外部函数寻找array[i]的定义,这里的i在for循环执行完之后变成了array[array.length-1]。

?

网上搜了一下,改进的方法有很多种:

方法1,外层包一个匿名函数,将每次循环的索引结果作为入参传入并自执行:

?

for(var i in array){
	(function(obj){
		var rowDiv = document.createElement("div");
		rowDiv.innerText = obj;
		rowDiv.onclick = function(){//注册点击事件
			alert(obj);
		};
	})(array[i]);
}

?

方法2,同样是利用闭包,给innerText赋值部分并没有问题,有问题的只是用到了闭包特性的onclick事件注册部分,所以就改onclick部分:

?

for(var i in array){
	var rowDiv = document.createElement("div");
	rowDiv.innerText = array[i];
	rowDiv.onclick = (function(obj){//注册点击事件
		return function(){
			alert(obj);
		};
	})(array[i]);
}

?

方法3,以上解决翻案的语法看起来挺晦涩,但都是在闭包里打转,个人觉得能不用闭包的地方就尽量别用,一个不小心就会碰上内存泄漏的问题。下面给出另一种解法:

for(var i in array){
	var rowDiv = document.createElement("div");
	rowDiv.innerText = array[i];
	rowDiv.obj = array[i];
	rowDiv.onclick = function(){//注册点击事件
		alert(this.obj);
	};
}

这是我个人认为较好的方案,给dom对象添加自定义属性来保存值,在div的事件触发时可以通过this来引用。

?