前言: 此篇文章稍长,请您用心阅读,完后我相信会解决你许多疑惑。javascript
咱们以一个 toy demo 开始.html
// dom level
-> div (onclick)
-> p
-> span
复制代码
问题🤔: 为何当咱们点击<p>
, <span>
click 事件也触发了?java
冒泡是事件的一种传递机制,当一个事件发生时,事件会以相反的顺序传播经过目标再父级祖先,最后以Window结束。面试
就拿以前的例子🌰来讲,当用户点击<span>
元素时,事件会从下至上(子元素->祖先)依次触发click事件。而且几乎全部的事件都会有冒泡阶段。数组
咱们知道在事件的传递机制中除了冒泡还有一种咱们常常提到的就是捕获机制。浏览器
其实这是不彻底正确的!!!,依据W3C的定义, 事件的传递分为了3阶段:bash
Capture(捕获阶段): 事件对象经过Window传播到目标的祖先父级再到自身。dom
Target(目标阶段): 事件对象到达目标自身, 当该事件类型指定不Bubble, 则事件将在此阶段终止(稍后解释)。ui
Bubble(冒泡阶段): 事件对象以相反的顺序传播经过目标的父级祖先,并以Window结束。this
因此当一个事件发生时标准的传递流为 捕获 -> 目标 -> 冒泡
当咱们程序在主流的浏览器运行时,咱们在 html 中使用 on[eventType]
或者 javascript 中使用 element. addEventListener(eventType, listener)
这些Web API像是忽略了 捕获阶段 只会运行 目标阶段 和 冒泡阶段。
问题🤔:那么这时候就产生了一个问题? 若咱们在一个嵌套的Dom上分别添加事件,咱们就不能改变事件的触发顺序(子元素绑定的事件会先于父元素绑定的事件触发),咱们如何改变这个顺序?
options ={}
| useCapture = boolean 】感谢 🙏 Web API 的完备性,若是你看过 addEventListener
API 的定义,你会发现,声明是有第三个参数选项的,它可传入一个 boolean 或者 一个对象。
elem.addEventListener(type, listener[, options]);
elem.addEventListener(type, listener[, useCapture]);
复制代码
因此当你在一个 元素 上添加对应的事件监听时, 你能够这样写:
elem.addEventListener(_, _, {capture: true})
// 对象的形式还接受更多的option:[once,...]
// 或者
elem.addEventListener(_, _, true)
复制代码
他们是等价的,表示监听的回调将会在 捕获阶段 被触发。那么这样咱们能够解决上面的问题。 例如:
// dom level
-> div (onclick= log('div', true))
-> p (onclick = log('p'))
-> span
// 此时输出顺序为 div -> p
// 相反
-> div (onclick= log('div'))
-> p (onclick = log('p'))
-> span
// 此时输出顺序为 p -> div
复制代码
首先对于listener来讲,它不只仅只能传入function
还能是一个对象但这个对象必须实现Event
接口(包含handleEvent(fn)
属性),在此咱们不对它过多解释,具体能够去参看文档.
接下来咱们具体说说 listener 被调用时传入的参数 Event 或 简写为 e
// dom level
-> div (onclick)
-> p
-> span
复制代码
仍是使用以前的例子,咱们解释几个误区(这里可能存在错误🙅,但愿大佬发现后不吝纠正):
⚠️误区 1 : 当<div>
添加了 click 事件,是否表明 <p>
, <span>
也添加了 click 事件
答: ❌, 事实上在 <div>
添加了 click 与它的子元素并无任何关系,可是能够经过 Event
对象拿到触发事件的真正对象,这看起来就好像是 <div>
的子元素一样添加了此事件监听。记住回调是发生在添加事件监听的目标元素身上的(click 的 listener callback 是发生在 div
上的)。
⚠️误区 2: 既然事件的传递分为 捕获 -> 目标 -> 冒泡,那么为何一次点击事件不会屡次触发。
答: 从以前的 addEventListener API 咱们知道 事件是能够绑定到 冒泡 或者 捕获 阶段的,当没有设置时默认是在 冒泡阶段 因此只会触发一次,那就是在对应绑定的阶段。
注意: 既然添加事件是区分阶段,那么在移除此事件时也须要明确对应阶段。
解释了上述误区,咱们回过来讲 e.target
: 当事件触发时,e.target
(read-only)的值为最深嵌套相关元素,并不必定为添加事件所对应元素。例如上诉例子,当你点击<span>
时事件传递冒泡走到<div>
元素其 click 事件回调被触发,而e.target
的值是 span
元素对象。
咱们知道了 e.target
(read-only) 并不必定为添加事件所对应元素,那么如何在回调中知道是那个元素添加的此事件监听呢? 这就是 e.currentTarget
,另外在 listener 方法内部也能够直接使用 this
,它等同于(this = event.currentTarget
)
e.path
(read-only)为一个数组eg: [span, p, div, body, html, document, Window]
它表示从 e.target
到 window
所经历到元素层级。
咱们在事件传递阶段讲 捕获阶段 的时候提到 能够提早终止不在进行冒泡阶段。 这是怎么作的了,其实能够在捕获阶段添加的监听事件回调被调用时候调用e.stopPropagation()
来阻止事件在DOM中进一步传播。 因为历史缘由也能够调用 e.cancelBubble = true
来阻止事件冒泡(但不建议使用此属性,最好使用 stopPropagation
方法)
Event
对象上还有许多属性,在这里不会所有罗列,最经常使用的基本上就是以上几个,其他属性还能够拿到不少信息,但部分属性可能并非标准
以上是有关EventListener一些常识,但愿你们可以不吝赐教👆
若是还有未涉及到的,欢迎提出讨论。
Why write this ?
主要是因为本人近期一次【bcz】面试的遭遇有关而发:
一是面谈中提到此话题因为我我的技艺不精不少细枝末节已经忘记,最终面试fail。主要问题在我,这也致使我想从新纪录下。
二是我尊重每一个公司的观念,但不表明我承认。经历【bcz】面试后,我认为贵公司,面试存在不少问题,这给个人感受是一家特别重视基础的公司(重视基础没有错)。拿面试官自身来讲经过交流我能感受到面试官对于知识也不够深刻。汲取知识应该终保持敬畏之心,既然大家十分重视基础那么大家最好作到权威,否则在半灌水的体量下你如何来评定? 若是你让面试者有这种感受,我只能认为你是在为了面试而面试,这不该该是应试教育。
三是写完个人自闭可能会缓解。