一、DOM事件
DOM事件分为捕获阶段,事件阶段和冒泡阶段。
- 捕获:当用户点击按钮,浏览器会从 window 从上向下遍历至用户点击的按钮,逐个触发事件处理函数。
- 冒泡:浏览器从用户点击的按钮从下往上遍历至 window,逐个触发事件处理函数。
- W3C 事件模型/事件机制:对每个事件先捕获再冒泡
我们可以看到,其执行顺序为,捕获 => 目标 => 冒泡
所以,如果用户点击了td元素,并且,td元素的祖先元素拥有监听函数,那么其祖先元素也算被点击了。
但是,如果被点击的元素没有父元素,且他被用户点击了,那么,冒泡与捕获的顺序取决于JS代码的顺序
DOM事件拥有捕获和冒泡阶段,那么监听函数会不会被调用两次呢?
答案是不会,W3C 制定了一个标准
xxx.addEventListener('eventType',fn,bool)
若bool为falsy值或不传(0,'',NaN,undefined,null),那么,绑定为冒泡,若为true则为捕获。
需要特别注意的是,如果我们需要延迟触发事件
那么直接监听,是监听不到事件的,因为1s后,点击事件已经结束了(currentTarget为null),所以我们需要将点击事件记录下来。
二、target VS currentTarget
target 与 currentTarget 的区别在于,e.target是用户操作的元素,e.currentTarget是程序员监听的元素。
例如 div>span{文字},那么用户点击文字
span就是target
div就是currentEvent
三、冒泡的取消
通常情况下,我们可以通过stopPropagation阻止冒泡(捕获无法被取消)。
(例如当我们点击一个内部标签时,不想触发外部标签的事件,可以使用这种方法)
例如,scroll 事件就无法取消冒泡,所以我们只能通过其他办法取消,例如使滚动条的宽度为0,阻止滚轮的默认事件,阻止移动端touchstart的默认事件。
x.addEventListener('wheel', (e)=>{
console.log(2)
e.preventDefault()
})
dom事件类型有很多,具体我们可以参考MDN
四、自定义事件
button1.addEventLsitener('click',()=>{
const event = new CustomEvent('name',{
{'detail':{name:'wang',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
}