这是一个经典问题,只不过有一段时间不写纯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来引用。
?