# DOM 事件
# 事件级别
DOM 0级
el.onclick = function() { }
当希望为同一个元素/标签绑定多个同类型的事件时(如给同一个按钮绑定三个点击事件),是不被允许的。DOM 0事件绑定,给元素的事件行为绑定方法,这些方法都是在当前元素事件行为的冒泡阶段(或者目标阶段)执行的
DOM 2级
el.addEventListener(eventName, callback, useCapture) // eventName: 事件名称,可以是标准的 DOM 事件 // callback: 回调函数,当事件触发时,函数会被注入一个参数为当前的事件对象 event // useCapture: 默认为false,代表事件句柄在冒泡阶段执行
DOM 3级,写法和 DOM 2级一致,只是在 DOM 2级事件的基础上添加了更多的事件类型
UI 事件:当前用户与页面的元素交互时触发:load,scroll
焦点事件:当元素获得或失去焦点时触发:blur,focus
鼠标事件:当用户通过鼠标在页面执行操作时触发:dblclick,mouseup
滚轮事件:当使用鼠标滚轮或类似设备时触发:mousewheel
文本事件:当在文档中输入文本时触发:textinput
键盘事件:当用户通过键盘在页面上执行操作时触发:keydown,keypress
合成事件:当为 IME(输入法编辑器)输入字符时触发:compositionstart
变动事件:当底层 DOM 结构发生变化时触发:DOMsubtreeModified
同时 DOM 3级事件也允许使用者自定义一些事件
# DOM 事件模型 事件流
事件模型分为:捕获和冒泡
事件流:
- 捕获阶段:事件从 window 对象自上而下向目标节点传播的阶段
- 目标阶段:真正的目标节点正在处理事件的阶段
- 冒泡阶段:事件从目标节点自下而上向 window 对象传播的阶段
# 事件委托(代理)
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方法叫事件的代理。
优点:
- 减少内存消耗,不需要为每个子元素绑定事件,提高性能
- 动态绑定事件
# Event 对象使用
阻止默认行为:
event.preventDefault()
阻止冒泡:
event.stopPropagation()
阻止事件冒泡到父元素,阻止任何父事件处理程序被执行event.stopImmediatePropagation()
既能阻止事件向父元素冒泡,也能阻止元素同事件类型的其他监听器被触发
event.target & event.currentTarget
<div id="a"> aaaa <div id="b"> bbbb <div id="c"> cccc <div id="d"> dddd </div> </div> </div> </div> <script> document.getElementById("a").addEventListener("click", function(e) { console.log( "target:" e.target.id + "& currentTarget:" + e.currentTarget.id ) }) document.getElementById("b").addEventListener("click", function(e) { console.log( "target:" e.target.id + "& currentTarget:" + e.currentTarget.id ) }) document.getElementById("c").addEventListener("click", function(e) { console.log( "target:" e.target.id + "& currentTarget:" + e.currentTarget.id ) }) document.getElementById("d").addEventListener("click", function(e) { console.log( "target:" e.target.id + "& currentTarget:" + e.currentTarget.id ) }) </script> <!-- 当我们点击最里层的元素 d 时,会依次输出: target: d & currentTarget: d target: d & currentTarget: c target: d & currentTarget: b target: d & currentTarget: a -->
由上述例子可知:
event.currentTarget
始终是监听事件者,而event.target
是事件的真正发出者
# 自定义事件
// 创建事件,Event 是无法传递参数的
var event = new Event('event')
// 创建参数,CustomEvent 允许传递参数
var event = new CustomEvent('event', { detail: 'Hello world' })
// 监听事件
el.addEventListener('event', function(e) {
// ...
}, false)
// 分发事件
el.dispatchEvent(event)
# 手写发布订阅模式
class EventEmitter {
events: {[key: string]: Function[]} = {}
// 订阅
on(type: string, callback: Function) {
if(!this.events) this.events = Object.create(null)
if(!this.events[type]) {
this.events[type] = [callback]
}else {
this.events[type].push(callback)
}
}
// 取消订阅
off(type: string) {
if(!this.events[type]) return
delete this.events[type]
}
// 只执行一次订阅
once(type: string, callback: Function) {
function fn() {
callback()
this.off(type)
}
this.on(type, fn)
}
// 触发事件
emit(type: string, ...rest) {
this.events[type] && this.events[type].forEach(fn => fn(...rest))
}
}
// 使用情况
const event = new EventEmitter()
event.on('click', (...rest) => {
console.log(rest)
})
event.emit('click')
event.off('click')
event.once('click', (...rest) => {
console.log(rest)
})