浏览器专题之事件机制
次访问
事件流
在早期 IE 和 Netscape 团队在开发第四代浏览器的时候,遇到一个问题:当点击一个按钮的时候,是应该先处理父级的事件呢?还是应该先处理按钮的事件呢?IE 和 Netscape 给出了 2 种完全相反的答案,IE 提出事件冒泡的概念,而 Netscape 则支持事件捕获。
事件冒泡
事件冒泡认为事件应该由最具体的元素开始触发,然后层层往父级传播:

事件捕获
而事件捕获则相反,认为最外层的元素应该最先收到事件,然后层层往下级传递:

DOM 事件流
为了在浏览器中兼容这 2 种事件流,在 DOM2 Events 规范中将事件流分为 3 个阶段:事件捕获阶段、到底目标阶段、事件冒泡阶段。

可以通过指定 addEventListener 的第三个参数为 true 来设置事件是在捕获阶段调用事件处理程序,默认是 false 指在冒泡阶段调用事件处理程序。
所有现代浏览器都支持
DOM事件流,只有 IE8 及更早版本不支持。
事件处理程序
HTML 事件处理程序
就是将事件处理程序直接绑定到 HTML 的属性中:
1 | // 方式一 |
HTML 事件处理程序修改事件相对麻烦,可能需要同时修改 HTML 和 JS,所以大家都不爱使用这种方式绑定事件。
DOM0 事件处理程序
将一个函数赋值给 DOM 元素的一个事件处理程序属性,比如 onclick:
1 | let btn = document.getElementById('div') |
DOM2 事件处理程序
通过 addEventListener 可以添加 DOM2 级别的事件处理程序,它接收 3 个参数:事件名、事件处理程序和 useCapture (它是一个可选参数,是个布尔值,默认为 false 表示在冒泡阶段调用事件处理程序)
1 | let btn = document.getElementById('div') |
和 DOM0 事件处理程序的区别:
addEventListener可以改变事件流,即可以在捕获阶段触发事件,而DOM0是不行的;addEventListener可以为同一个元素多次添加同一类型的事件处理程序,先添加的事件处理程序会先触发,而DOM0如果给同一个元素绑定多个相同类型的事件处理程序的话,则后面添加的会覆盖前面定义的;
它有几个注意事项:
- 如果不需要在捕获阶段进行拦截操作,则
useCapture即第三个参可以不传; - 通过
addEventListener添加的事件处理程序只能通过removeEventListener移除,而且绑定的事件处理程序必须是同一个。
1 | let btn = document.getElementById('div') |
IE 事件处理函数
由于 addEventListener 无法兼容 IE8 及更早版本,所以此时就可以使用 attachEvent 添加事件处理程序和用 detachEvent 移除事件处理程序。
1 | let btn = document.getElementById('div') |
它有这么几个注意事项:
- 注册的事件名和
DOM0一样,需要带上on,比如onclick; - 在通过
attachEvent添加的事件处理程序内部this会指向window,而DOM0和DOM2的this会指向元素本身; - 和
addEventListener一样,attachEvent也可以针对同一元素多次添加同一个事件类型的处理程序,但是触发顺序是后定义的先触发; - 通过
detachEvent移除事件处理程序的时候,处理函数必须是和注册的同一个,这点和addEventListener保持一致;
attachEvent 和 detachEvent 是 IE 专属的 API,所以如果有兼容性要求,我们可以写出跨浏览器的事件处理程序:
1 | var EventUtil = { |
事件对象
通过不同的事件处理程序添加的事件,event 对象的属性略有不同,我们不需要记住他们的差异,只需要在平时写代码的时候养成一个写兼容代码的习惯即可,如下是一个兼容各种 event 对象的事件处理程序:
1 | let handler = function(event) { |
事件类型
DOM3 Events 定义了如下事件类型:
- 用户界面事件(
UIEvent):涉及与BOM交互的通用浏览器事件,比如onload、resize、scroll、input、select等; - 焦点事件(
FocusEvent):在元素获得和失去焦点时触发,比如focus、blur; - 鼠标事件(
MouseEvent):使用鼠标在页面上执行某些操作时触发,比如click、mousedown、mouseover等; - 滚轮事件(
WheelEvent):使用鼠标滚轮(或类似设备)时触发,比如mousewheel; - 输入事件(
InputEvent):向文档中输入文本时触发,比如textInput; - 键盘事件(
KeyboardEvent):使用键盘在页面上执行某些操作时触发,比如keydown、keypress; - 合成事件(
CompositionEvent):在使用某种IME(Input Method Editor,输入法编辑器)输入字符时触发,比如compositionstart。
事件委托
事件委托是指将多个元素上绑定的事件通过利用事件冒泡的原理从而转移到他们共同的父级上去绑定,从而在一定程度上起到优化的作用,有的人也喜欢叫它事件代理。比如在 Vue 中经常会将事件绑定到每个列表项中:
1 | <ul> |
1 | handleClick(event) { |