事件级别
-
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) })