事件委托
事件委托是利用事件冒泡原理实现的,如果对多个dom节点绑定相同的事件,而这些节点有相同的祖先元素,则可以只绑定祖先元素,在事件冒泡到祖先元素时再执行事件源的对应的回调函数
//html部分
<ul>
<span>gsdgsf</span>
<li>a<span>>>>>></span></li>
<li>b</li>
<li>c</li>
<li>d</li>
</ul>
//js代码部分
//事件委托函数的封装,element代表委托给的dom对象,seletor代表目标事件源的选择器,eventType表示事件类型,fn表示执行函数
function delegation(element,seletor,eventType,fn){
function callee(){
var e=event||window.event,
el = e.target||e.srcElement//兼容IE10及以下的事件源
while(!el.matches(seletor)){
if(el===element){
return
}
el=el.parentNode
}
el&&fn.call(el,e,el)
}
//兼容IE,addEventListener对应IE的attachEvent
if(window.attachEvent){
/*IE10及以下的事件回调函数的this指向window,利用bind方法将this指向绑定的dom对象
*但是bind在ie8不兼容,所以如果要使用bind需要添加bind方法,但也可以更改为:
*element.attachEvent('on'+eventType,function(){callee.call(element)})
*/
element.attachEvent('on'+eventType,callee.bind(element))
}
else{
element.addEventListener(eventType,callee)
}
}
//解决matches的兼容问题:matches主要是用来判断当前DOM节点是否能匹配对应的CSS选择器规则;如果匹配成功,返回true,反之则返回false,在这我们用来匹配需要委托的dom节点
if(!Element.prototype.matches){
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||//兼容火狐
Element.prototype.msMatchesSelector || //兼容IE
Element.prototype.oMatchesSelector || //兼容opera
Element.prototype.webkitMatchesSelector||//兼容Chrome
//兼容IE8
function(seletor){
var el = (this.document||this.ownerDocument).querySelectorAll(seletor),n=el.length
while(--n>=0&&el.item(n)!=this);
return n>-1
}
}
//解决ie8不支持bind
if(!Function.prototype.bind){
Function.prototype.bind=function(){
if(typeof this!=='function'){
throw this+" is not a function"
}
var obj = arguments[0],
fn=this,
_arguments=Array.prototype.slice.call(arguments,1),
bindF=function(){
fn.apply(this instanceof bindF?this:obj,_arguments.concat(arguments))
}
function f1(){}
f1.prototype=fn.prototype
bindF.prototype=new f1()
return bindF
}
}
//测试
function fn(event,el){
console.log(el.innerText)
}
var ul = document.getElementsByTagName('ul')[0]
//将ul的后代元素的事件委托给ul
delegation(ul,'ul *','click',fn)
这个事件委托函数我测试过Chrome、edge、ie8及以上(以下的不行,报错:Element未定义),基本是可行的,如果有问题欢迎指出和探讨~
补充:关于事件冒泡
js事件流包括事件捕获阶段、事件目标阶段、事件冒泡阶段三个阶段,一个事件是从根节点开始向内捕获(事件捕获),直到到目标元素停止,触发回调函数(目标阶段),然后原路返回,向外触发祖先元素绑定的同类型事件的回调函数(事件冒泡)。
我们可以从一个例子上观察这三个阶段:利用addEventListener绑定事件,addEventListener有三个参数,第一个表示事件类型,第二个表示触发事件的回调函数,第三个是布尔值,true表示捕获阶段时触发事件,默认值为false表示冒泡阶段时触发。默认event为事件对象,事件对象的target表示目标元素,currentTarget表示当前触发事件的元素
//元素布局
<div style="height:60px;width:60px" id="1">1
<div style="height:40px;width:40px" id="2">2
<div style="height:20px;width:20px" id="3">3
</div>
</div>
</div>
//绑定事件
var divs = document.getElementsByTagName('div')
for(var i=0,n=divs.length;i<n;i++){
divs.item(i).addEventListener("click",(e)=>{
console.log(`target:${e.target.id} currentTarget:${e.currentTarget.id}`)
})
}
//
点击3所在的div元素,冒泡时触发事件,从目标元素向外分别执行回调函数,冒泡路径:[div#3, div#2, div#1, body, html, document, Window]
输出:
target:3 currentTarget:3
target:3 currentTarget:2
target:3 currentTarget:1
//捕获时触发事件
var divs = document.getElementsByTagName('div')
for(var i=0,n=divs.length;i<n;i++){
divs.item(i).addEventListener("click",(e)=>{
console.log(`target:${e.target.id} currentTarget:${e.currentTarget.id}`)
},true)
}
点击3,捕获时触发事件,所以从外向内分别执行回调函数
输出:
target:3 currentTarget:1
target:3 currentTarget:2
target:3 currentTarget:3