1. DOM事件模型
1.1 调用顺序
<div class="oldfather">
<div class="father">
<div class="son">
文字
</div>
</div>
</div>
分别为三个div添加事件监听为:fnYe/ fnBa /fnEr
fnYe -> fnBa -> fnEr 的调用顺序叫做事件捕获。
fnEr -> fnBa -> fnEr 的调用顺序叫做事件冒泡。
事件捕获:从外向内寻找监听函数。
事件冒泡:从内向外寻找监听函数。
1.2 DOM事件模型
事件触发:
当添加事件的监听时,由开发者决定事件捕获阶段是否触发,冒泡阶段是否触发。
事件查询:
当事件被触发后,会先按照事件捕获的顺序查询是否有事件触发,再按照事件冒泡的顺序查询是否有事件触发。
事件绑定:
EventTarget.addEventListener('click',fn,bool)
参数bool决定是否在冒泡阶段被触发。默认为false,不传或falsy先冒泡,true先捕获。只会在冒泡阶段或捕获阶段触发,不能在两个阶段都触发。
1.3 冒泡无效
场景:当用户监听的元素与用户操作元素一致时,addEventListener()的bool参数无效。 因为在上述场景不存在标签的父子关系,哪个事件先监听,哪个事件先触发。
1.4 冒泡终止
event.stopPropagation()可以中断冒泡,让浏览器对事件冒泡的查询终止。
2.事件委托
2.1 事件委托
场景:当需要给100个button设置事件监听,如何处理?
通过给button加上一个共有的父标签,利用事件冒泡,监听事件。
<div id="div1">
<button>按钮1</button>
<button>按钮2</button>
...
<button>按钮100</button>
</div>
div1.addEventListener('click',(e)=>{
const t = e.target
if(t.tagName.toLowerCase() === 'button'){
console.log('点击了button')
console.log(t.textContent)
}
})
设置对共有的父标签的监听,监听其子标签的事件触发。
通过获取触发事件的元素内容,得知具体的触发元素。
通过这种方式,仅需要一个监听器就能实现,该方式被称作事件委托。事件委托能够对还未存在的元素进行监听。
更好的方式是通过给标签加入属性data-id实现。
<div id="div1">
<button data-id='1'>按钮1</button>
<button data-id='2'>按钮2</button>
...
<button data-id='100'>按钮100</button>
</div>
为每个标签设置属性data-id。
div1.addEventListener('click',(e)=>{
const t = e.target
if(t.tagName.toLowerCase() === 'button'){
console.log('点击了button')
console.log(t.dataset.id)
}
})
并通过.dataset.id获取其data-id。
2.2 封装事件委托
输入:事件类型,监听元素,匹配具体元素的选择表达式,函数动作。
输出:用户触发元素的data-id。
function on(eventType,element,selector,fn){
if(!(element instanceof Element)){
element = document.querySelector(element)
}
element.addEventListener(eventType,(event)=>{
const target = event.target
if(target.matches(selector)){
fn(event)
}
})
}
对先前事件委托的实现进行了封装。
.matches()用于获取元素内满足选择器条件的元素。
当参数element为选择器时,获取其选择器对应的元素。
2.3 递归判断
存在一个问题,场景如下:
<div id="div1">
<button data-id='1'><span>按钮1</span></button>
<button data-id='2'><span>按钮2</span></button>
...
<button data-id='100'><span>按钮3</span></button>
</div>
当button内用span标签存储文字内容,此时用户点击的是span标签。
通过on('click','#div1','button',fn)这种方式进行事件委托,需要将参数'button'更改为'span'。
如此每次进行事件委托,都需要确定具体的html结构,很麻烦,无法根据视图直接决定selector参数。
因此需要通过一个递归判断,用户操作的元素的父级衍生是否存在button。 因此需要通过一个递归判断,用户操作的元素的父级衍生是否存在button。
function on(eventType,element,selector,fn){
if(!(element instanceof Element)){
element = document.querySelector(element)
}
element.addEventListener(eventType,(event)=>{
const target = event.target
while(!target.matches(selector)){
if(element === target){
target = null
break
}
target = target.parentNode
}
target ? fn.call(target,element,target) : console.log('没有满足选择器'+selector+'的元素')
})
}
逻辑:target为用户操作的元素,当用户操作的元素与参数selector的条件不匹配,则获取target的父元素,并在判断是否与selector匹配,直到找到匹配的元素,并作为fn的参数。若一直找到element,则不存在匹配的元素。