DOM 事件模型或 DOM 事件机制
事件是用户或者浏览器自己执行的某种动作,是文档或者浏览器发生的一些交互瞬间,比如点击(click)按钮等,这里的click就是事件的名称。JS与html之间的交互是通过事件实现的,DOM支持大量的事件。
一个事件发生后,会在子元素及父元素之间进行传播(propagation),,这种传播分为三个阶段。
- 从外到内找监听函数就是事件捕获
- 在目标节点触发事件
- 从内到外找监听函数就是事件冒泡
事件捕获:一个事件被触发时,浏览器会自动从用户操作标签外的最上级标签逐渐向里检查是否有相同事件,如果有则触发,如果没有则继续向下检查知道用户操作的标签的过程。
事件冒泡:会继续由用户操作标签继续向是上级标签检查,如果有相同事件则触发,如果没有则继续向上检查直到最上级元素为止的过程
事件传播的最上层对象是window,上例的事件传播顺序,在捕获阶段依次为window、document、html、body、父节点、目标节点,在冒泡阶段依次为目标节点、父节点、body、html、document、window。
在捕获和冒泡都进行监听时,先捕获在冒泡,除只有一个div被监听时,先监听哪个就执行哪个
点击事件
<div class="grandfather">
<div class="father">
<div class="son">文本内容</div>
</div>
</div>
-
当点击文本内容时,点击了谁?
可以说点击了son,也可以点击了father,也可以点击了grandfather,它们都算。
-
给三个div分别添加事件的监听fnYe/fnBa/fnSon, 调用顺序是怎样的?
都可以,fnYe到fnBa到fnSon / fnSon到fnBa到fnYe。但由于IE认同后者和网景认同前者,所以W3C发布标准。
规定浏览器应该同时支持两种调用顺序
首先按照grandfather->father->son
然后按照son->father->grandfather
addEventListener事件绑定API
IE5* : baba.attachEvent('onclick',fn)//冒泡
网景 : baba.addEventListener('click',fn)//捕获
W3C: baba.addEventListener('click',fn,bool)
其中bool为true时,fn走捕获
bool不传 或falsy,fn走冒泡
target v.s. currentTarget的区别
currentTarget事件属性返回其监听器触发事件的节点,即当前处理该事件的元素、文档或窗口。
target 事件属性可返回事件的目标节点(触发该事件的节点),用户点击的。
区别:
e.target - 用户操作的元素
e.currentTarget-程序员监听的元素
this是e.currentTarget,我个人不推荐使用它
例如:
div>span{文字},用户点击文字
e.target就是span
e.currentTarget就是div
取消冒泡(捕获不可以取消但是冒泡可以)
e.stopPropagation()可打断冒泡,一般用于封装某些独立组件
事件委托
事件委托:委托一个元素帮我去监听我本应该要监听的事件(click事件)
自定义事件:
button1.addEventListener('click', ()=>{
const event = new CustomEvent("cheng", {"detail":{name:'cheng', age: 20}})
button1.dispatchEvent(event)
})
button1.addEventListener('cheng', (e)=>{
console.log('cheng')
console.log(e)
})
给100个按钮添加点击事件:
div1.addEventListener('click',(e)=>{
const t = e.target
if(t.tagName.toLowerCase()==='button'){
console.log('button 被点击了')
}
})//toLowerCase小写
监听目前不存在的元素的点击事件:
监听祖先,等点击的时候看看是不是监听的元素即可。
setTimeout(()=>{
const button = document.createElement('button')
button.textContent='click 1'
div1.appendChild(button)
},1000)
div1.addEventListener('click',(e)=>{
const t = e.target
if(t.tagName.toLowerCase()==='button'){
console.log('button被click')
}
})
封装一个事件委托
方法一(错的但是面试可以用):
setTimeout(()=>{
const button = document.createElement('button')
button.textContent='click 1'
div1.appendChild(button)
},1000)
on('click','#div1','button',()=>{//'#div'是选择器不是元素
console.log('button 被点击了')
})
function on(eventType,element,selector,fn){
if(!(element instanceof Element)){
element = document.querySelector(element)
}
element.addEventListener(eventType,(e)=>{
const t= e.target//被点击的元素是span不是button
if(t.matches(selector)){//matches用来判断一个元素是否匹配一个selector(选择器)
fn(e)
}
})
}
方法二:
on: function(element, eventType, selector, fn) {
element.addEventListener(eventType, e => {
let el = e.target
while (!el.matches(selector)) {
if (element === el) {
el = null
break
}
el = el.parentNode
}
el && fn.call(el, e, el)
})
return element
},