若是一个元素和它的祖先元素注册了同一类型的事件函数(例如点击等), 那么当事件发生时事件函数调用的顺序是什么呢? 浏览器
好比, 考虑以下嵌套的元素:函数
----------------------------------- | outer | | ------------------------- | | |inner | | | ------------------------- | | | -----------------------------------
两个元素都有onclick
的处理函数. 若是用户点击了inner
, inner
和outer
上的事件处理函数都会被调用. 但谁先谁后呢?this
在刚刚过去的那些糟糕年代, Netscape和M$对此有不一样的见解. spa
Netscape认为outer
上的处理函数应该先被执行. 这被称做event capturing
. code
M$则认为inner
上的处理函数具备执行优先权. 这被叫作event bubbling
. blog
两种见解针锋相对事件
当使用事件捕获时ip
| | ---------------| |----------------- | outer | | | | -----------| |----------- | | |inner \ / | | | ------------------------- | | Event CAPTURING | -----------------------------------
outer
上的事件处理器先触发, 而后是inner
上的element
/ \ ---------------| |----------------- | outer | | | | -----------| |----------- | | |inner | | | | | ------------------------- | | Event BUBBLING | -----------------------------------
与事件捕获相反, 当使用事件冒泡时, inner上的事件处理器先被触发, 其后是outer上面的开发
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$模型中, 没有对e.currentTarget
的支持, 更糟糕的是, this
也不指向当前的HTML元素.