事件委托函数

87 阅读3分钟
事件委托

事件委托是利用事件冒泡原理实现的,如果对多个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

参考:juejin.cn/post/685512…