Open Bulandent opened 3 years ago
在早期 IE 和 Netscape 团队在开发第四代浏览器的时候,遇到一个问题:当点击一个按钮的时候,是应该先处理父级的事件呢?还是应该先处理按钮的事件呢?IE 和 Netscape 给出了 2 种完全相反的答案,IE 提出事件冒泡的概念,而 Netscape 则支持事件捕获。
IE
Netscape
事件冒泡
事件冒泡认为事件应该由最具体的元素开始触发,然后层层往父级传播:
事件捕获
而事件捕获则相反,认为最外层的元素应该最先收到事件,然后层层往下级传递:
DOM 事件流
DOM
为了在浏览器中兼容这 2 种事件流,在 DOM2 Events 规范中将事件流分为 3 个阶段:事件捕获阶段、到底目标阶段、事件冒泡阶段。
DOM2 Events
可以通过指定 addEventListener 的第三个参数为 true 来设置事件是在捕获阶段调用事件处理程序,默认是 false 指在冒泡阶段调用事件处理程序。
addEventListener
true
false
所有现代浏览器都支持 DOM 事件流,只有 IE8 及更早版本不支持。
HTML 事件处理程序
HTML
就是将事件处理程序直接绑定到 HTML 的属性中:
// 方式一 <div onclick="console.log('hello world')"></div> 方式二 <div onclick="print(event)"></div> <script> function print(e) { } </script>
HTML 事件处理程序修改事件相对麻烦,可能需要同时修改 HTML 和 JS,所以大家都不爱使用这种方式绑定事件。
JS
DOM0 事件处理程序
DOM0
将一个函数赋值给 DOM 元素的一个事件处理程序属性,比如 onclick:
onclick
let btn = document.getElementById('div') // 添加事件 btn.onclick = function() { } // 移除事件 btn.onclick = null
DOM2 事件处理程序
DOM2
通过 addEventListener 可以添加 DOM2 级别的事件处理程序,它接收 3 个参数:事件名、事件处理程序和 useCapture (它是一个可选参数,是个布尔值,默认为 false 表示在冒泡阶段调用事件处理程序)
useCapture
let btn = document.getElementById('div') btn.addEventListener('click', () => { }, false)
和 DOM0 事件处理程序的区别:
它有几个注意事项:
removeEventListener
let btn = document.getElementById('div') let handler = function() { } btn.addEventListener("click", handler) btn.removeEventListener("click", handler)
IE 事件处理函数
由于 addEventListener 无法兼容 IE8 及更早版本,所以此时就可以使用 attachEvent 添加事件处理程序和用 detachEvent 移除事件处理程序。
attachEvent
detachEvent
let btn = document.getElementById('div') btn.attachEvent("onclick", function() { })
它有这么几个注意事项:
on
this
window
attachEvent 和 detachEvent 是 IE 专属的 API,所以如果有兼容性要求,我们可以写出跨浏览器的事件处理程序:
API
var EventUtil = { addHandler: function(element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false) } else if (element.attachEvent) { element.attachEvent("on" + type, handler) } else { element["on" + type] = handler; } }, removeHandler: function(element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false) } else if (element.detachEvent) { element.detachEvent("on" + type, handler) } else { element["on" + type] = null } } }
通过不同的事件处理程序添加的事件,event 对象的属性略有不同,我们不需要记住他们的差异,只需要在平时写代码的时候养成一个写兼容代码的习惯即可,如下是一个兼容各种 event 对象的事件处理程序:
event
let handler = function(event) { // 事件对象 let event = event || window.event // 目标元素 let target = event.target || event.srcElement // 阻止默认事件触发 if (event.preventDefault) { event.preventDefault() } else { event.returnValue = false } // 阻止事件冒泡 if (event.stopPropagation) { event.stopPropagation() } else { event.cancelBubble = true } }
DOM3 Events 定义了如下事件类型:
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 中经常会将事件绑定到每个列表项中:
Vue
<ul> <li v-for="item in list" :key="item" @click="handleClick(item)">{{item}}</li> </ul>
其实更好的做法是利用事件委托,将事件绑定到 ul 上:
ul
<ul @click="handleClick"> <li v-for="item in list" :key="item" :data-item="item">{{item}}</li> </ul>
handleClick(event) { let target = event.target if (target === 'li') { let data = target.dataset.item } }
事件流
在早期
IE
和Netscape
团队在开发第四代浏览器的时候,遇到一个问题:当点击一个按钮的时候,是应该先处理父级的事件呢?还是应该先处理按钮的事件呢?IE
和Netscape
给出了 2 种完全相反的答案,IE
提出事件冒泡的概念,而Netscape
则支持事件捕获。事件冒泡
事件冒泡认为事件应该由最具体的元素开始触发,然后层层往父级传播:
事件捕获
而事件捕获则相反,认为最外层的元素应该最先收到事件,然后层层往下级传递:
DOM
事件流为了在浏览器中兼容这 2 种事件流,在
DOM2 Events
规范中将事件流分为 3 个阶段:事件捕获阶段、到底目标阶段、事件冒泡阶段。可以通过指定
addEventListener
的第三个参数为true
来设置事件是在捕获阶段调用事件处理程序,默认是false
指在冒泡阶段调用事件处理程序。事件处理程序
HTML
事件处理程序就是将事件处理程序直接绑定到
HTML
的属性中:HTML
事件处理程序修改事件相对麻烦,可能需要同时修改HTML
和JS
,所以大家都不爱使用这种方式绑定事件。DOM0
事件处理程序将一个函数赋值给
DOM
元素的一个事件处理程序属性,比如onclick
:DOM2
事件处理程序通过
addEventListener
可以添加DOM2
级别的事件处理程序,它接收 3 个参数:事件名、事件处理程序和useCapture
(它是一个可选参数,是个布尔值,默认为false
表示在冒泡阶段调用事件处理程序)和
DOM0
事件处理程序的区别:addEventListener
可以改变事件流,即可以在捕获阶段触发事件,而DOM0
是不行的;addEventListener
可以为同一个元素多次添加同一类型的事件处理程序,先添加的事件处理程序会先触发,而DOM0
如果给同一个元素绑定多个相同类型的事件处理程序的话,则后面添加的会覆盖前面定义的;它有几个注意事项:
useCapture
即第三个参可以不传;addEventListener
添加的事件处理程序只能通过removeEventListener
移除,而且绑定的事件处理程序必须是同一个。IE 事件处理函数
由于
addEventListener
无法兼容 IE8 及更早版本,所以此时就可以使用attachEvent
添加事件处理程序和用detachEvent
移除事件处理程序。它有这么几个注意事项:
DOM0
一样,需要带上on
,比如onclick
;attachEvent
添加的事件处理程序内部this
会指向window
,而DOM0
和DOM2
的this
会指向元素本身;addEventListener
一样,attachEvent
也可以针对同一元素多次添加同一个事件类型的处理程序,但是触发顺序是后定义的先触发;detachEvent
移除事件处理程序的时候,处理函数必须是和注册的同一个,这点和addEventListener
保持一致;attachEvent
和detachEvent
是IE
专属的API
,所以如果有兼容性要求,我们可以写出跨浏览器的事件处理程序:事件对象
通过不同的事件处理程序添加的事件,
event
对象的属性略有不同,我们不需要记住他们的差异,只需要在平时写代码的时候养成一个写兼容代码的习惯即可,如下是一个兼容各种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
中经常会将事件绑定到每个列表项中:其实更好的做法是利用事件委托,将事件绑定到
ul
上: