热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JS性能优化之事件委托

面试中2次被问到过这个知识点,实际开发中,应用事件委托也比较常见。JS中事件委托的实现主要依赖于事件冒泡。那什么是事件冒泡?就是事件从最深

面试中2次被问到过这个知识点,实际开发中,应用事件委托也比较常见。JS中事件委托的实现主要依赖于 事件冒泡 。那什么是事件冒泡?就是事件从最深的节点开始,然后逐步向上传播事件,举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。

 

为什么要用事件委托呢?一是为了图方便,二是为了提升性能。具体我们来举个例子看下:

HTML结构代码

<ul id&#61;"box"><li>demoli><li>demoli><li>demoli><li>demoli>...<li>demoli><li>demoli><li>demoli>
ul>

 

我们现在要给 ul 下的 li 添加点击事件。如果你不知道或不会运用事件委托&#xff0c;你的代码可能是下面这样的&#xff1a;

var box &#61; document.getElementById(&#39;box&#39;),lis &#61; box.getElementsByTagName(&#39;li&#39;);for (var i &#61; lis.length - 1; i >&#61; 0; i--) {lis[i].onclick &#61; function() {}
}

上面代码很简单&#xff0c;逻辑清晰。我们看看有多少次的dom操作&#xff0c;首先要找到ul&#xff0c;然后遍历li&#xff0c;然后点击li的时候&#xff0c;又要找一次目标的li的位置&#xff0c;才能执行最后的操作&#xff0c;每次点击都要找一次li。我们再来看下应用 事件委托 后的代码&#xff1a;

var box &#61; document.getElementById(&#39;box&#39;);
box.onclick
&#61; function() { }

这里用父级ul做事件处理&#xff0c;当li被点击时&#xff0c;由于冒泡原理&#xff0c;事件就会冒泡到ul上&#xff0c;因为ul上有点击事件&#xff0c;所以事件就会触发&#xff0c;当然&#xff0c;这里当点击ul的时候&#xff0c;也是会触发的&#xff0c;那么问题就来了&#xff0c;如果我想让事件代理的效果跟直接给节点的事件效果一样怎么办&#xff0c;比如说只有点击li才会触发&#xff1f;

Event对象提供了一个属性叫target&#xff0c;可以返回事件的目标节点&#xff0c;我们成为事件源&#xff0c;也就是说&#xff0c;target就可以表示为当前的事件操作的dom&#xff0c;但是不是真正操作dom&#xff0c;当然&#xff0c;这个是有兼容性的&#xff0c;标准浏览器用ev.target&#xff0c;IE浏览器用event.srcElement&#xff0c;此时只是获取了当前节点的位置&#xff0c;并不知道是什么节点名称&#xff0c;这里我们用nodeName来获取具体是什么标签名&#xff0c;这个返回的是一个大写的&#xff0c;我们需要转成小写再做比较&#xff08;习惯问题&#xff09;&#xff1a;

var box &#61; document.getElementById("box");
box.onclick
&#61; function(ev) {var ev &#61; ev || window.event;var target &#61; ev.target || ev.srcElement;if(target.nodeName.toLowerCase() &#61;&#61;&#61; &#39;li&#39;){}
}

这样改下就只有点击li会触发事件了&#xff0c;且每次只执行一次dom操作&#xff0c;如果li数量很多的话&#xff0c;将大大减少dom的操作&#xff0c;优化的性能可想而知&#xff01;

 

上面的例子是说li操作的是同样的效果&#xff0c;要是每个li被点击的效果都不一样&#xff0c;那么用事件委托还有用吗&#xff1f;

<div id&#61;"box"><input type&#61;"button" id&#61;"add" value&#61;"添加" /><input type&#61;"button" id&#61;"remove" value&#61;"删除" /><input type&#61;"button" id&#61;"move" value&#61;"移动" /><input type&#61;"button" id&#61;"select" value&#61;"选择" />
div>

常规思路代码&#xff1a;

var Add &#61; document.getElementById("add");
var Remove &#61; document.getElementById("remove");
var Move &#61; document.getElementById("move");
var Select &#61; document.getElementById("select");Add.onclick &#61; function(){alert(&#39;添加&#39;);
};
Remove.onclick
&#61; function(){alert(&#39;删除&#39;);
};
Move.onclick
&#61; function(){alert(&#39;移动&#39;);
};
Select.onclick
&#61; function(){alert(&#39;选择&#39;);
}

 

上面实现的效果我就不多说了&#xff0c;很简单&#xff0c;4个按钮&#xff0c;点击每一个做不同的操作&#xff0c;那么至少需要4次dom操作&#xff0c;如果用事件委托&#xff0c;能进行优化吗&#xff1f;

var oBox &#61; document.getElementById("box");
oBox.onclick
&#61; function (ev) {var ev &#61; ev || window.event;var target &#61; ev.target || ev.srcElement;if(target.nodeName.toLocaleLowerCase() &#61;&#61; &#39;input&#39;){switch(target.id){case &#39;add&#39; :alert(&#39;添加&#39;);break;case &#39;remove&#39; :alert(&#39;删除&#39;);break;case &#39;move&#39; :alert(&#39;移动&#39;);break;case &#39;select&#39; :alert(&#39;选择&#39;);break;}}
}

用事件委托就可以只用一次dom操作就能完成所有的效果&#xff0c;比上面的性能肯定是要好一些的 。

 

现在讲的都是document加载完成的现有dom节点下的操作&#xff0c;那么如果是新增的节点&#xff0c;新增的节点会有事件吗&#xff1f;也就是说&#xff0c;一个新员工来了&#xff0c;他能收到快递吗&#xff1f;看一下正常的添加节点的方法&#xff1a;

<input type&#61;"button" name&#61;"" id&#61;"btn" value&#61;"添加" />
<ul id&#61;"ul1"><li>111li><li>222li><li>333li><li>444li>
ul>

现在是移入li&#xff0c;li变红&#xff0c;移出li&#xff0c;li变白&#xff0c;这么一个效果&#xff0c;然后点击按钮&#xff0c;可以向ul中添加一个li子节点

var oBtn &#61; document.getElementById("btn");
var oUl &#61; document.getElementById("ul1");
var aLi &#61; oUl.getElementsByTagName(&#39;li&#39;);
var num &#61; 4;//鼠标移入变红&#xff0c;移出变白
for(var i&#61;0; i){aLi[i].onmouseover &#61; function(){this.style.background &#61; &#39;red&#39;;};aLi[i].onmouseout &#61; function(){this.style.background &#61; &#39;#fff&#39;;}}//添加新节点oBtn.onclick &#61; function(){num&#43;&#43;;var oLi &#61; document.createElement(&#39;li&#39;);oLi.innerHTML &#61; 111*num;oUl.appendChild(oLi);};

 

 这是一般的做法&#xff0c;但是你会发现&#xff0c;新增的li是没有事件的&#xff0c;说明添加子节点的时候&#xff0c;事件没有一起添加进去&#xff0c;这不是我们想要的结果&#xff0c;那怎么做呢&#xff1f;一般的解决方案会是这样&#xff0c;将for循环用一个函数包起来&#xff0c;命名为mHover&#xff0c;如下&#xff1a;

var oBtn &#61; document.getElementById("btn");
var oUl &#61; document.getElementById("ul1");
var aLi &#61; oUl.getElementsByTagName(&#39;li&#39;);
var num &#61; 4;function mHover () {//鼠标移入变红&#xff0c;移出变白for(var i&#61;0; i){aLi[i].onmouseover &#61; function(){this.style.background &#61; &#39;red&#39;;};aLi[i].onmouseout &#61; function(){this.style.background &#61; &#39;#fff&#39;;}}
}
mHover ();
//添加新节点
oBtn.onclick &#61; function(){num&#43;&#43;;var oLi &#61; document.createElement(&#39;li&#39;);oLi.innerHTML &#61; 111*num;oUl.appendChild(oLi);mHover ();
};

 

 虽然功能实现了&#xff0c;看着还挺好&#xff0c;但实际上无疑是又增加了一个dom操作&#xff0c;在优化性能方面是不可取的&#xff0c;那么有事件委托的方式&#xff0c;能做到优化吗&#xff1f;

var oBtn &#61; document.getElementById("btn");
var oUl &#61; document.getElementById("ul1");
var aLi &#61; oUl.getElementsByTagName(&#39;li&#39;);
var num &#61; 4;//事件委托&#xff0c;添加的子元素也有事件
oUl.onmouseover &#61; function(ev){var ev &#61; ev || window.event;var target &#61; ev.target || ev.srcElement;if(target.nodeName.toLowerCase() &#61;&#61; &#39;li&#39;){target.style.background &#61; "red";}};
oUl.onmouseout
&#61; function(ev){var ev &#61; ev || window.event;var target &#61; ev.target || ev.srcElement;if(target.nodeName.toLowerCase() &#61;&#61; &#39;li&#39;){target.style.background &#61; "#fff";}};//添加新节点oBtn.onclick &#61; function(){num&#43;&#43;;var oLi &#61; document.createElement(&#39;li&#39;);oLi.innerHTML &#61; 111*num;oUl.appendChild(oLi);};

 

 看&#xff0c;上面是用事件委托的方式&#xff0c;新添加的子元素是带有事件效果的&#xff0c;我们可以发现&#xff0c;当用事件委托的时候&#xff0c;根本就不需要去遍历元素的子节点&#xff0c;只需要给父级元素添加事件就好了&#xff0c;其他的都是在js里面的执行&#xff0c;这样可以大大的减少dom操作&#xff0c;这才是事件委托的精髓所在。

 

现在给一个场景 ul > li > div > p&#xff0c;div占满li&#xff0c;p占满div&#xff0c;还是给ul绑定时间&#xff0c;需要判断点击的是不是li&#xff08;假设li里面的结构是不固定的&#xff09;&#xff0c;那么e.target就可能是p&#xff0c;也有可能是div&#xff0c;这种情况你会怎么处理呢&#xff1f;

那我们现在就再现一下这个场景&#xff1a;

<ul id&#61;"test"><li><p>11111111111p>li><li><div>22222222div>li><li><span>3333333333span>li><li>4444444li>
ul>

 

如上列表&#xff0c;有4个li&#xff0c;里面的内容各不相同&#xff0c;点击li&#xff0c;event对象肯定是当前点击的对象&#xff0c;怎么指定到li上&#xff0c;下面我直接给解决方案&#xff1a;

var oUl &#61; document.getElementById(&#39;test&#39;);
oUl.addEventListener(
&#39;click&#39;,function(ev){var target &#61; ev.target;while(target !&#61;&#61; oUl ){if(target.tagName.toLowerCase() &#61;&#61; &#39;li&#39;){console.log(&#39;li click~&#39;);break;}target &#61; target.parentNode;}
})

 

核心代码是while循环部分&#xff0c;实际上就是一个递归调用&#xff0c;你也可以写成一个函数&#xff0c;用递归的方法来调用&#xff0c;同时用到冒泡的原理&#xff0c;从里往外冒泡&#xff0c;知道currentTarget为止&#xff0c;当当前的target是li的时候&#xff0c;就可以执行对应的事件了&#xff0c;然后终止循环&#xff0c;恩&#xff0c;没毛病&#xff01;这里看不到效果&#xff0c;大家可以复制过去运行一下&#xff01;

 

转载自&#xff1a; https://www.cnblogs.com/liugang-vip/p/5616484.html

转:https://www.cnblogs.com/similar/p/8502660.html



推荐阅读
  • 本文介绍了如何利用JavaScript或jQuery来判断网页中的文本框是否处于焦点状态,以及如何检测鼠标是否悬停在指定的HTML元素上。 ... [详细]
  • 导航栏样式练习:项目实例解析
    本文详细介绍了如何创建一个具有动态效果的导航栏,包括HTML、CSS和JavaScript代码的实现,并附有详细的说明和效果图。 ... [详细]
  • 前言--页数多了以后需要指定到某一页(只做了功能,样式没有细调)html ... [详细]
  • 本文详细介绍了如何解决Uploadify插件在Internet Explorer(IE)9和10版本中遇到的点击失效及JQuery运行时错误问题。通过修改相关JavaScript代码,确保上传功能在不同浏览器环境中的一致性和稳定性。 ... [详细]
  • 主要用了2个类来实现的,话不多说,直接看运行结果,然后在奉上源代码1.Index.javaimportjava.awt.Color;im ... [详细]
  • 本文介绍了如何使用JQuery实现省市二级联动和表单验证。首先,通过change事件监听用户选择的省份,并动态加载对应的城市列表。其次,详细讲解了使用Validation插件进行表单验证的方法,包括内置规则、自定义规则及实时验证功能。 ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 本文介绍了在Windows环境下使用pydoc工具的方法,并详细解释了如何通过命令行和浏览器查看Python内置函数的文档。此外,还提供了关于raw_input和open函数的具体用法和功能说明。 ... [详细]
  • 深入理解 Oracle 存储函数:计算员工年收入
    本文介绍如何使用 Oracle 存储函数查询特定员工的年收入。我们将详细解释存储函数的创建过程,并提供完整的代码示例。 ... [详细]
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • 本文深入探讨了 Java 中的 Serializable 接口,解释了其实现机制、用途及注意事项,帮助开发者更好地理解和使用序列化功能。 ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 在使用 MUI 框架进行应用开发时,开发者常常会遇到 mui.init() 和 mui.plusReady() 这两个方法。本文将详细解释它们的区别及其在不同开发环境下的应用。 ... [详细]
  • 本文讨论了如何根据特定条件动态显示或隐藏文件上传控件中的默认文本(如“未选择文件”)。通过结合CSS和JavaScript,可以实现更灵活的用户界面。 ... [详细]
author-avatar
dujiaolianglong
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有