面试中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
这是一般的做法&#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
}
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