DOM事件和事件委托

806 阅读2分钟

DOM事件

DOM事件分为捕获阶段,事件阶段和冒泡阶段。

我们可以看到,其执行顺序为,捕获 => 目标 => 冒泡

所以,如果用户点击了td元素,并且,td元素的祖先元素拥有监听函数,那么其祖先元素也算被点击了。

但是,如果被点击的元素没有父元素,且他被用户点击了,那么,冒泡与捕获的顺序取决于JS代码的顺序

上文提到,DOM事件拥有捕获和冒泡阶段,那么监听函数会不会被调用两次呢?

答案是不会,W3C 制定了一个标准,其api为

xxx.addEventListener('eventType',fn,bool)

若bool为falsy值或不传(0,'',NaN,undefined,null),那么,绑定为冒泡,若为true则为捕获。

需要特别注意的是,如果我们需要延迟触发事件

那么直接监听,是监听不到事件的,因为1s后,点击事件已经结束了(currentTarget为null),所以我们需要将点击事件记录下来。


target VS currentTarget

target 与 currentTarget 的区别在于,target是用户操作的元素,currentTarget是程序员监听的元素。

例如 div>span{文字},那么用户点击文字

span就是target

div就是currentEvent


冒泡的取消

通常情况下,我们可以通过stopPropagation阻止冒泡(捕获无法被取消)。

(例如当我们点击一个内部标签时,不想触发外部标签的事件,可以使用这种方法)

例如,scroll 事件就无法取消冒泡,所以我们只能通过其他办法取消,例如使滚动条的宽度为0,阻止滚轮的默认事件,阻止移动端touchstart的默认事件。


自定义事件

dom事件类型有很多,具体我们可以参考MDN

button1.addEventLsitener('click',()=>{
    const event = new CustomEvent('name',{
        {'detail':{name:'jack',age:'18'}},
        bubbles:true,
        cancelable:true
    })
    button1.dispatchEvent(event)
})

事件委托

事件委托就是监听祖先元素,判断被点击的元素是否是我们想监听的元素,若是,则执行函数。 其优点是节省内存以及可以监听动态生成的元素

function on(eventType,element,selector,fn){
    if(!(element instanceof Element)){
        element = document.querySelector(element)
    }
    
    element.addEventListener(eventType,(e)=>{
        t = e.target
        if (t.tagName.toLowercase() === selector){
            fn(e)
        }
    })
}

如果我们将事件委托函数封装成以上形式,我们会发现一个问题,当用户点击时,target不一定是我们监听的元素。

所以,我们需要使用递归,来寻找target的祖先元素,直到祖先元素是element为止。

function on(eventType,element,selector,fn){
    if (!(element instanceof Element)){
        element = document.querySelector(element)
    }
    
    element.addEventListener(eventType,(e)=>{
        let t = e.target
        while(t.tagName.toLowercase() !== selector){
            if(t === element){
                t = null
                break
            }
            t = t.parentNode
        }
        t && fn.call(t,e,t)
    })
    return element
}