一、DOM事件模型
先看一段代码:
<div class= 爷爷 >
<div class= 爸爸>
<div class= 儿子>
文字
</div>
</div>
</div>
给3个 div 分别添加事件监听 fnYe、fnBa、fnEr
问1:点击谁
(1) 点击了文字,算不算点击了儿子?
(2) 点击了文字,算不算点击了爸爸?
(3) 点击了文字,算不算点击了爷爷?
3个都算
问2:调用顺序
点击文字,最先调用哪个函数 fnYe?fnBa?fnEr
都可以?浏览器的不同调用的顺序就不同:IE认为先调 fnEr ,网景认为先调fnYe
w3c制定标准
- 规定浏览器同时支持两种调用顺序
- 首先按 爷爷 >> 爸爸 >> 儿子 顺序看有没有事件监听函数 (从外到内的捕获)
- 然后按 儿子 >> 爸爸 >> 爷爷 顺序看有没有事件监听函数 (从内到外的冒泡)
- 有监听函数就调用,并提供监听内容,没有就跳过
问:那么函数都要执行两遍?
错,由 开发者 来决定,函数放在 捕获阶段 还是放在 冒泡阶段
DOM事件模型分为捕获和冒泡。
一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。
- 捕获阶段:事件从window对象自上而下向目标节点传播的阶段;
- 目标阶段:真正的目标节点正在处理事件的阶段;
- 冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。
示意图:
关于事件绑定api -------- x.addEventListener('click',fn,bool)
二、事件委托
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。
优点一:减少内存消耗,提高性能
场景1:如何给100个按钮添加点击事件
监听给这些个按钮的祖先,冒泡的时候判断 target 是否是这些个按钮中的一个
实现如下
优点二:动态绑定事件
场景2:如何监听目前不存在的元素的点击事件
监听元素的祖先,点击的时候看是不是想要监听的元素
实现如下
三、关于其他
1. target vs currentTarget
区别
- target :用户操作的元素
- currentTarget:程序员监听的元素
举例
- div > span {文字},用户点击 span
- e.target 是 span
- e.currentTarget 是 div
特别的它1
背景
- 只有一个 div 被监听时(不考虑父子同时被监听)
- fn 分别在捕获阶段和冒泡阶段监听事件
- 用户点击的元素就是开发者监听的元素
- 先执行哪个函数?
- 谁先监听谁就先执行
实现如下
2. 取消冒泡 ------ e.stopPropagation
但是捕获不可取消
事件特性
- Bulles 表示是否冒泡
- Cancelable 是否支持开发者取消冒泡 有些事件不可取消冒泡 如scroll事件
特别的它2 ------ 如何阻止滚动
- 阻止 scroll 事件的默认动作没用,因为先有滚动才会有滚动事件
操作如下 示例
- 找准滚动条所在的元素
- 取消默认动作 ------- e.preventDefault()
- 取消 滚轮 默认动作
- 隐藏 滚动条
- 移动端 取消触屏的默认动作
// js部分
// 取消滚轮默认动作
div1.addEventListener('wheel',(e=>{
e.preventDefault()
}))
// 移动端 取消触屏的默认动作
div1.addEventListener('touchstart',(e=>{
e.preventDefault()
}))
// css 部分
// 隐藏 滚动条 注意:这个方法不兼容IE,自定义滚动条的伪对象选择器::-webkit-scrollbar
::-webkit-scrollbar { width: 0 !important }
3. 自定义事件
- 创建自定义事件 : const event = new CustomEvent ("对象名", {内容1,内容2、、、}
- 触发事件 :.dispatchEvent(event)
例:给 buttom 自定义一个 chili 事件,并且默认可以冒泡,也不能阻止冒泡。效果
<div id=div1>
<button id=button1>点击触发 chili 事件
</button>
</div>
button1.addEventListener('click', () => {
const event = new CustomEvent("chili", {
"detail": {
name: 'chili',
age: 18
},
bubbles:true,
cancelable:false
})
button1.dispatchEvent(event)
})
div1.addEventListener('chili', (e) => {
console.log('chili')
console.log(e)
console.log(e.detail)
})
4. 封装事件委托
- 写一个函数 on('click','#div1','button',fn)
- 当用户点击 #div1 里面的 button 元素时,调用fn 函数
方法1:判断 target 是否匹配 button
<div id=div1></div>
setTimeout(()=>{
const button = document.createElement('button')
button.textContent = 'click'
div1.appendChild(button)
},1000)
on('click','#div1','button',()=>{
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
if(t.matches(selector)){ //判断元素是否被指定的选择器字符串所选择
fn(e)
}
})
}
**方法2:判断 target的爸爸、爷爷、、、是否匹配 button
function on(eventType,element,selector,fn){
element.addEventListener(eventType, e => {
let t = e.target
while (!t.matches(selector)) {
if (element === t) {
t = null
break
}
t = t.parentNode
}
t && fn.call(t, e, t)
})
}