因为事件处理程序在现代web应用中可以实现交互,所以很多开发者都会错误地在页面中大量使用它们,在JavaScript中,页面中事件处理程序的数量与页面整体性能直接相关。原因有很多,比如①每个函数都是对象,都要占用内存空间,对象越多,性能越差;②为指定事件处理程序所需访问DOM的次数会先造成整个页面交互的延迟。
for(let value of values){
ul.innerHTML += '${value} ';
}
这段代码效率低,因为每次迭代都要设置一次innerHTML,不仅如此,每次循环都要先读取innerHTML,也就是说一次循环要访问两次innerHTML。
let itemsHtml = "";
for(let value of values){
itemsHtml += '${value} ';
}
ul.innerHTML = itemsHtml;
这样修改之后,效率就高多了,只会对innerHTML进行一次赋值,下面代码也可以搞定:
ul.innerHTML = values.map(value => '
过多事件处理程序的解决方案是使用事件委托。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。例如,click事件冒泡到document。这意味着可以为整个页面指定一个onclick事件处理程序,而不是为每个可点击元素分别指定事件处理程序。
<ul id="myGirls">
<li id="girl1">比比东</li>
<li id="girl2">云韵</li>
<li id="girl3">美杜莎</li>
</ul>
这里包含三个列表项,在被点击时应该执行某个操作,通常的方式是指定三个事件处理程序:
let item1 = document.getElementById("girl1");
let item2 = document.getElementById("girl2");
let item3 = document.getElementById("girl3");
item1.addEventListener("click",(event) => {
console.log("我是比比东!");
})
item2.addEventListener("click",(event) => {
console.log("我是云韵!");
})
item3.addEventListener("click",(event) => {
console.log("我是美杜莎!");
})
相同代码太多,代码过于丑陋了。
使用事件委托,只要给多有元素的共同的祖先节点添加一个事件处理程序,就可以解决丑陋!
let list = document.getElementById("myGirls");
list.addEventListener("click",(event) => {
let target = event.target;
switch(target.id){
case "girl1":
console.log("我是比比东!");
break;
case "girl2":
console.log("我是云韵!");
break;
case "girl3":
console.log("我是美杜莎!");
break;
}
})
把事件处理程序指定给元素后,在浏览器代码和负责页面交互的JavaScript代码之间就建立了联系。这种联系简历越多,页面性能就越差。除了通过事件委托来限制这种连接之外,还应该及时删除不用的事件处理程序。很多web应用性能不佳都是由于无用的事件处理程序长驻内存导致的。
导致这个问题的原因有两个:
比如通过的DOM方法removeChild()或replaceChild()删除节点。最常见的还是使用innerHTML整体替换页面的某一部分。这时候,被innerHTML删除的元素上如果有事件处理程序,也不会被垃圾收集程序正常清理。
所以,如果在得知某个元素会被删除之前,应手动删除它的事件处理程序,比如btn.onclick = null;//删除事件处理程序
,事件委托也有助于解决这个问题,如果得知某个元素要被innerHTML替代的时候,就不要给该元素添加事件处理程序了,将其添加到更高层级的节点上即可。
如果在页面卸载后事件处理程序没有被清理,则它们仍然会残留在内存中。之后,浏览器每次加载和卸载页面(比如通过前进、后退或刷新),内存中残留对象的数量都会增加,这是因为事件处理程序不会被回收。
一般来说,最好在onunload事件处理程序中趁页面尚未卸载先删除所有事件处理程序。这时候也能体现出事件委托的优势,因为事件处理程序少,所以容易记住删除哪些。
let divs = document.getElementsByTagName("div");
for(let i = 0;i<divs.length;++i){
let div = document.createElement("div");
document.body.appendChild(div);
}
let divs = document.getElementsByTagName("div");
for(let i = 0,len=divs.length;i<len;++i){
let div = document.createElement("div");
document.body.appendChild(div);
}
表达式①中第一行取得了包含文档中所有i < divs.length
,这意味着要获取所有divs.length
,因此表达式①会造成死循环。
而表达式②中,又初始化了一个保存集合长度的变量len,因为len保存着循环开始集合的长度,而这个值不会随集合增大动态增长(for循环中初始化变量处只会初始化一次
),所以就可以避免表达式①中出现的无穷循环问题。
如果不想初始化一个变量,也可以使用反向迭代:
表达式③
let divs = document.getElementsByTagName("div");
for(let i = divs.length-1;i>=0;--i){
let div = document.createElement("div");
document.body.appendChild(div);
}
七、JavaScript思维导图
八、关注公众号哪吒编程,回复1024,获取Java学习资料,还有不定期的送书活动