JavaScript 中的事件流模型 事件冒泡
和 事件捕获
,以及 事件委托
(也叫事件代理),是前端面试中经常出现的知识点,作为一名前端工程师,梳理基础知识点对你一定有所帮助。
文章中所有的代码都有 codepen 实例,链接在对应的章节内,请同学们尝试自己修改运行代码,以便加深理解。
一、名词解释
在开始讲解之前,我们先熟悉几个概念
事件
事件是可以被 JavaScript 侦测到的行为。
如 onclick
onload
onchange
等事件。
事件流
事件在页面中的响应顺序
事件流模型
为了更好的理解事件流模型,我们把 DOM 树想象成一个靶子,父节点在外,子节点在内。如下图所示:
-
事件冒泡(event bubbling)
由内向外,即从 DOM 树的子到父,div -> body -> html -> document
-
事件捕获(event capturing)
由外向内,即从 DOM 树的父到子,document -> html -> body -> div
接下来我们先通过代码实例详细讲解事件冒泡和事件捕获,然后讲解事件委托,并实现一个事件委托的实例。
二、事件冒泡 vs. 事件捕获
代码地址: https://codepen.io/cecillia/p...
事件冒泡
和 事件捕获
分别由 微软
和 网景
公司提出,后来 W3C
将两者结合,平息了战火,制定了统一的标准 —— 先捕获再冒泡。
addEventListener
在 JavaScript 中,addEventListener
方法用于向指定元素添加事件句柄。
语法:element.addEventListener(event, function, useCapture)
element | 目标元素 | |
event | 事件名,如 click | |
function | 事件触发时执行的函数 | |
useCapture | Bool值,true - 事件句柄在 捕获 阶段执行,false- false- 默认。事件句柄在 冒泡 阶段执行 |
事件冒泡
来看一段代码实例,思考运行后会弹出什么。
/**.html**/
document
html
body
div
/**.js**/
var $t0 = document.getElementsByClassName('t0')[0];
var $t1 = document.getElementsByClassName('t1')[0];
var $t2 = document.getElementsByClassName('t2')[0];
var $t3 = document.getElementsByClassName('t3')[0];
$t0.addEventListener("click", function(){
alert("click div")
}, false);
$t1.addEventListener("click", function(){
alert("click body")
}, false);
$t2.addEventListener("click", function(){
alert("click html")
}, false);
$t3.addEventListener("click", function(){
alert("click document")
}, false);
根据冒泡事件流模型由内向外的规则,会依次弹出:click div -> click body -> click html -> click docuement
事件捕获
将上一段代码中的 false
都改为 ture
,则变为捕获方式:
/**.html**/
document
html
body
div
/**.js**/
var $t0 = document.getElementsByClassName('t0')[0];
var $t1 = document.getElementsByClassName('t1')[0];
var $t2 = document.getElementsByClassName('t2')[0];
var $t3 = document.getElementsByClassName('t3')[0];
$t0.addEventListener("click", function(){
alert("click div")
}, true);
$t1.addEventListener("click", function(){
alert("click body")
}, true);
$t2.addEventListener("click", function(){
alert("click html")
}, true);
$t3.addEventListener("click", function(){
alert("click document")
}, true);
根据捕获事件流模型由外向内的规则,会依次弹出:click document -> click html -> click body -> click div
事件冒泡&事件捕获同时存在
如果两种事件流模型同时存在会怎样展示呢?
/**.html**/
document
html
body
div
/**.js**/
var $t0 = document.getElementsByClassName('t0')[0];
var $t1 = document.getElementsByClassName('t1')[0];
var $t2 = document.getElementsByClassName('t2')[0];
var $t3 = document.getElementsByClassName('t3')[0];
$t0.addEventListener("click", function(){
alert("click div")
}, false);
$t1.addEventListener("click", function(){
alert("click body")
}, false);
$t2.addEventListener("click", function(){
alert("click html")
}, true);
$t3.addEventListener("click", function(){
alert("click document")
}, true);
原则:
- 从外向内,捕获前进,遇到捕获事件立即执行
- 非 target 节点,先捕获再冒泡
- target 节点,按代码书写顺序执行(无论冒泡还是捕获)
因此会依次弹出:click document -> click html -> click div -> click body
三、事件流模型的应用:事件委托
代码地址: https://codepen.io/cecillia/p...
事件委托
又叫 事件代理
,指的是利用事件冒泡原理,只需给外层父容器添加事件,若内层子元素有点击事件,则会冒泡到父容器上,这就是事件委托,简单说就是:子元素委托它们的父级代为执行事件。
事件流模型在业务开发中有哪些应用场景呢?
例如,一个播放列表有成千上万首歌曲,如果遍历播放列表,给每个 item 添加点击事件,而这样无疑会多次访问 DOM,每次 DOM 操作都会引起浏览器的重绘与重排,非常不利于性能优化。
这时候我们可以利用事件委托,只给最外层的容器添加响应事件,这样只需一次 DOM 操作,就能达到目的。
/**.html**/
- 青花瓷
- 东风破
- 双节棍
/**.js**/
var $music = document.getElementById('music');
$music.addEventListener('click', function(e) {
if(e.target.nodeName.toLowerCase() === 'li') { // 判断目标元素target是否为li元素
var content = e.target.innerHTML;
console.log(content);
}
}, false)
怎么样,事件委托也不过如此吧?只要掌握了事件冒泡、事件捕获的原理,并将其运用到实际业务开发中,就能够真正 get 事件委托这个知识点啦~
【欢迎指正,码字不易,喜欢请点赞哦】