若是在没有敲代码实际运行的状况下,能确定且正确的回答如下问题,则能够不继续往下看。若是对某个题的回答存在疑虑或不清楚怎么回事,能够选择继续阅读。javascript
<div id="div1" onclick="console.log(7)">
<div id="div2" onclick="console.log(6)"></div>
</div>
复制代码
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
div2.onclick = function () { console.log(5) }
div1.addEventListener('click', function () { console.log(1) })
div2.addEventListener('click', function () { console.log(2) }, false)
div1.addEventListener('click', function () { console.log(3) }, { capture: true })
div2.addEventListener('click', function () { console.log(4) }, { capture: true })
复制代码
若是继续阅读的话,上面的问题,会在正文中有分析。html
注:如无特殊说明,本文以chrome测试的结果为准java
当在页面上某个元素触发特定事件时,页面上哪些部分会触发该事件?现代浏览器开发者设定,除了被点击的目标元素,全部祖先元素都会触发该事件,一直到window(现代浏览器,IE4和网景是document)。chrome
这样又出现了新的问题,在window和目标元素都触发事件,那是先在目标元素上触发呢,仍是先在其余元素上触发呢?这就是事件流的概念。浏览器
事件流是事件在目标元素和祖先元素间的触发顺序,在早期,IE和网景实现了相反的事件流,IE4实现的是先触发目标元素的事件,再向上一层一层触发祖先元素的事件,到document对象(即事件冒泡);bash
但现代浏览器实现的事件流是DOM2级的事件标准,包含了IE、网景的实现,并且都把window也包含在内即,div -> body -> html -> document -> window
。函数
DOM2级事件流标准有三个阶段:事件捕获阶段、出于目标阶段、事件冒泡阶段。先发生在事件捕获阶段,而后到目标元素,最后再冒泡上去到window。测试
既然在冒泡阶段和捕获阶段都会触发事件,那当添加了事件监听方法以后,是否是在每一个元素上都每次事件都会触发两次呢?显然不是,在DOM2中事件监听机制提供了一个参数来决定事件是在捕获阶段生效仍是在冒泡阶段生效,即addEventListenerui
// DOM2级事件监听方法
// useCapture(可选):Boolean,表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
// 默认为false,即在冒泡阶段触发
target.addEventListener(type, listener[, useCapture]);
// 目前的事件监听方法
// options(可选): 一个指定有关 listener 属性的可选参数对象。可用的选项以下:
// capture: Boolean,表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
// once: Boolean,表示 listener 在添加以后最多只调用一次。若是是 true, listener 会在其被调用以后自动移除。
// passive: Boolean,表示 listener 永远不会调用 preventDefault()。若是 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
target.addEventListener(type, listener[, options]);
复制代码
在日常开发中,不多须要用到第三个参数,使用的是默认的false值,因此不少时候并无弄清楚,第三个参数有没有、true、false有什么区别。 能够验证事件流的过程:spa
<div id="div1"> <div id="div2"></div> </div>
复制代码
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
// capture默认为false,在事件流到达目标以后再网上传递,比目标元素上的事件触发晚
div1.addEventListener('click', function () { console.log(1) })
div2.addEventListener('click', function () { console.log(2) }, false)
// capture为true,在到达目标元素前的捕获阶段在div1上触发,最早执行回调
div1.addEventListener('click', function () { console.log(3) }, { capture: true })
div2.addEventListener('click', function () { console.log(4) }, { capture: true })
复制代码
div2是目标元素,在它上面capture为true是否会先触发? 其实当事件流处于于目标阶段后,事件的回调函数会按照注册的顺序触发,而无论capture是false仍是true,详情参考连接。
因此上例的输出顺序就比较明显了,3, 2, 4, 1
事件监听的方式有三种:
经过HTML属性的方式<div onclick="console.log('click')"></div>
DOM0中能够经过js脚原本给指定元素提供事件处理函数,即
element.onclick = handlerhandler为匿名函数或指定的函数名
复制代码
在DOM2中,添加了新的事件监听API,即addEventListener(type, handler[, options | useCapture])
,同时提供了取消监听的removeEventListener(type, handler[, options | useCapture])
;显然事件处理函数注册后,要取消监听,type/hanlder/useCapture的一致。
相比html属性方式、DOM0级监听方式,addEventListener的优点是什么呢?主要有如下几点:
addEventLinster可为同一个事件注册多个回调函数,以此触发。而DOM0级注册会覆盖
addEventLinster能够经过参数决定监听是在冒泡阶段生效,仍是在捕获阶段生效。element.onclick注册的监听只会在冒泡阶段生效
更方便移除监听
沿用前面的例子:
<div id="div1">
<div id="div2" onclick="console.log(7)"></div>
</div>
复制代码
const div2 = document.getElementById('div2')
// 若是同时使用了HTML属性方式,和DOM0方式,则DOM0方式会覆盖HTML属性方式
div2.onclick = function () { console.log(8) }
// 一样这一个会覆盖掉上一条,只会log出9
div2.onclick = function () { console.log(9) }
div2.addEventLisnter('click', function () { console.log(10) })
div2.addEventLisnter('click', function () { console.log(11) }) // 不会覆盖,会log出9, 10, 11
复制代码
DOM2能够经过removeEventListener的方式移除处理函数,HTML属性方式注册和DOM0的监听如何移除呢?
div2.onclick = null
// or
div2.setAttributer('onclick', false)
复制代码
这两个方法均可以把HTML属性注册或element.onclick方式注册的监听移除,但不会影响addEventListener注册的监听。
到这里,最开始问题1中的输出顺序也容易了
<div id="div1" onclick="console.log(7)">
<div id="div2" onclick="console.log(6)"></div>
</div>
复制代码
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
div2.onclick = function () { // 覆盖掉html中的onclick属性,6不会被输出,同时在冒泡阶段,这个方法最早被注册
console.log(5)
}
div1.addEventListener('click', function () { console.log(1) })
div2.addEventListener('click', function () { console.log(2) }, false)
div1.addEventListener('click', function () { console.log(3) }, { capture: true })
div2.addEventListener('click', function () { console.log(4) }, { capture: true })
复制代码
结果是: 3, 5, 2, 4, 7, 1,这里须要注意的是5先于二、4被log出来,而7先于1被log出来(在chrome中的结果)
参考: