如下html代码,如何给一个列表中所有子元素添加点击事件,输出对应内容?
<ul id="container">
<li id="li1">1</li>
<li id="li2">2</li>
<li id="li3">3</li>
<li id="li4">4</li>
<li id="li5">5</li>
</ul>
首先,循环列表,依次添加点击事件:
for(var i = 1; i < 5; i++){
document.getElementById('li' + i).addEventListener('click', function() {
alert(i);
})
}
似乎很容易理解这段代码,但是实际上,不管点击哪个<li>
标签,都会弹出数字5。还不清楚原因的同学,可以先来了解一下html事件处理程序和js作用域的相关知识。
有个很直接的方法,每次输出li标签对应的innerText即可。如果我一定要输出这个i呢?可以使用闭包的方式:
for(var i = 1; i < 5; i++){
(function(x){
document.getElementById('li' + x).addEventListener('click', function(){
alert(x)
})
})(i)
}
这里形如(function(){})()
的代码块,叫做“立即被调用的函数表达式”(Immediately Invoked Function Expression)。可以思考,这里闭包如何产生?
另外,采用ES6新增的变量类型let,也可以快速的解决该问题。
for(let i = 1; i < 5; i++){
document.getElementById('li' + i).addEventListener('click', function() {
alert(i);
})
}
用let代替var来声明变量,就可以把变量的作用域限制在当前代码块中。变量i只存在于for循环中,一旦循环结束,在其他位置均无法访问该变量。
上面方法都是通过循环,通过addEventListener
方法分别给每个<li>
标签添加点击事件。这样做完全OK,不会导致什么问题。
但是在javaScript中,添加到页面上的事件处理程序的数量,将直接关系到页面的整体运行性能。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先制定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。
想象以上示例中,<li>
标签的数量很大时,循环为每个子元素添加事件,绝非好方法。下面给出一种优雅的方法,采用事件委托。
document.getElementById('container').addEventListener('click', function(event) {
var target = event.target;
if(target.tagName == 'LI'){
alert(target.innerText);
}
}, false);
这段代码里,使用事件委托只为<ul>
元素添加一个onclick
事件处理程序。因为有事件冒泡机制,单击每个<li>
标签时,都会被这个函数处理。关于事件流,主要为事件捕获阶段与事件冒泡阶段,可以另行了解。
参阅学习文档: