题目
假如一个元素和它的先人元素注册了统一范例的事宜函数(比方点击等), 那末当事宜发作时事宜函数挪用的递次是什么呢?
比方, 斟酌以下嵌套的元素:
-----------------------------------
| outer |
| ------------------------- |
| |inner | |
| ------------------------- |
| |
-----------------------------------
两个元素都有onclick
的处置惩罚函数. 假如用户点击了inner
, inner
和outer
上的事宜处置惩罚函数都邑被挪用. 但谁先谁后呢?
两个模子
在方才过去的那些蹩脚年代, Netscape和M$对此有差别的意见.
Netscape以为outer
上的处置惩罚函数应当先被实行. 这被称作event capturing
.
M$则以为inner
上的处置惩罚函数具有实行优先权. 这被叫做event bubbling
.
两种意见针锋相对
事宜捕捉(event capturing)
当运用事宜捕捉时
| |
---------------| |-----------------
| outer | | |
| -----------| |----------- |
| |inner \ / | |
| ------------------------- |
| Event CAPTURING |
-----------------------------------
outer
上的事宜处置惩罚器先触发, 然后是inner
上的
事宜冒泡(event bubbling)
/ \
---------------| |-----------------
| outer | | |
| -----------| |----------- |
| |inner | | | |
| ------------------------- |
| Event BUBBLING |
-----------------------------------
与事宜捕捉相反, 当运用事宜冒泡时, inner上的事宜处置惩罚器先被触发, 厥后是outer上面的
W3C 模子
W3C规范则取其折衷计划. W3C事宜模子中发作的任何事宜, 先(从其先人元素window
)最先一起向下捕捉, 直到抵达目的元素, 厥后再次从目的元素最先冒泡.
1. 先从上往下捕捉
|
| | / \
-----------------| |--| |-----------------
| outer | | | | |
| -------------| |--| |----------- |
| | inner \ / | | | |
| | | | |
| | 2. 抵达目的元素后从下往上冒泡| |
| -------------------------------- |
| W3C event model |
------------------------------------------
而你作为开发者, 能够决议事宜处置惩罚器是注册在捕捉或许是冒泡阶段. 假如addEventListener
的末了一个参数是true
, 那末处置惩罚函数将在捕捉阶段被触发; 不然(false), 会在冒泡阶段被触发.
比方以下的代码:
var selector = document.querySelector.bind(document);
selector('div.outer').addEventListener('click', (e) => {
selector('p:first-of-type').textContent += 'outer clicked! '
}, true)
selector('div.inner').addEventListener('click', (e) => {
selector('p:first-of-type').textContent += 'inner clicked! '
}, false)
document.addEventListener('click', (e) => {
selector('p:first-of-type').textContent += 'document clicked! '
}, true)
当点击inner
元素时, 以下事变发作了:
点击事宜最先于捕捉阶段. 在此阶段, 浏览器会在
inner
的一切先人元素上查找点击事宜处置惩罚函数(从window
最先).效果找到了2个, 分别在
document
和outer
上面, 而且这两个事宜处置惩罚函数的useCapture
选项为true
, 申明它们是被注册在捕捉阶段的. 因而,document
和outer
的点击处置惩罚函数被实行了.继承向下寻觅, 直到抵达
inner
元素自身. 捕捉阶段就此结束. 此时进入冒泡阶段,inner
上的事宜处置惩罚器获得实行.事宜掷中目的元素后最先向上冒泡, 一起查找是不是有注册了冒泡阶段的先人元素上的事宜处置惩罚器. 因为没有找到, 因而什么也没发作.
末了的效果是:
假如我们把先人元素的事宜处置惩罚器注册在冒泡阶段的话(addEventListener
的useCapture
选项为false
):
var selector = document.querySelector.bind(document);
selector('div.outer').addEventListener('click', (e) => {
selector('p:first-of-type').textContent += 'outer clicked! '
console.log(e);
}, false)
selector('div.inner').addEventListener('click', (e) => {
selector('p:first-of-type').textContent += 'inner clicked! '
console.log(e);
}, false)
document.addEventListener('click', (e) => {
selector('p:first-of-type').textContent += 'document clicked! '
}, false)
效果则是:
传统模子
element.onclick = function(){}
将被注册在冒泡阶段.
事宜冒泡的运用
比方: 当点击时的默许函数
假如在document
上注册一个点击函数:
document.addEventlistener('click', (e) => {}, false)
那末任何元素上的点击事宜末了都邑冒泡到这个事宜处置惩罚器上并触发函数 – 除非前面的事宜处置惩罚函数阻挠了冒泡(e.stopPropogation()
, 在这类情况下事宜不会继承向上冒泡)
注重: e.stopPropagation()
只能阻挠事宜在冒泡阶段的向上流传. 假如被点击元素的先人元素有注册在捕捉阶段的事宜处置惩罚器:
ancestorElem.addEventListner('click', (e) => {
// do something...
}, true)
那末该先人元素上的事宜处置惩罚器还是会在捕捉阶段被触发.
因而, 你能够在document
上设置这么一个处置惩罚函数, 当页面上的任何元素被点击时, 这个处置惩罚函数就被会触发. 一个有用的例子就是下拉菜单: 当点击文档上除下拉菜单自身时恣意一处时, 下拉菜单会被隐蔽.
在冒泡或许捕捉阶段, e.currentTarget
指向当前事宜处置惩罚函数所附着的元素. 你也能够用事宜处置惩罚函数内的this
取而代之.
M$模子的贫苦
在M$模子中, 没有对e.currentTarget
的支撑, 更蹩脚的是, this
也不指向当前的HTML元素.