7

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**/
<div class="t3">document
  <div class="t2">html
    <div class="t1">body
      <div class="t0">div</div>
    </div>
  </div>
</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**/
<div class="t3">document
  <div class="t2">html
    <div class="t1">body
      <div class="t0">div</div>
    </div>
  </div>
</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**/
<div class="t3">document
  <div class="t2">html
    <div class="t1">body
      <div class="t0">div</div>
    </div>
  </div>
</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**/
<ul id="music">
  <li>青花瓷</li>
  <li>东风破</li>
  <li>双节棍</li>
</ul>

/**.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 事件委托这个知识点啦~


【欢迎指正,码字不易,喜欢请点赞哦】


Chen
718 声望47 粉丝

若你喜欢怪人 其实我很美