JS15 JavaScript中的事件流

当点击页面的某个按钮时,单击事件并不仅仅发生在按钮上。换句话说,在单机按钮的同时,也点击了按钮的容器元素,甚至也单机了整个页面。

==事件流描述的是从页面中接受时间的顺序==,IE和Netscape提出了几乎相反的事件流的概念:

  1. IE的事件流是事件冒泡流
  2. Netscape是事件捕获流
image

==大多是情况下都是将事件处理程序添加到事件冒泡阶段==

事件冒泡

IE的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(嵌套层次最深的节点)接受,然后逐级向上传播,直至文档根节点。

事件冒泡是自内而外的去触发各个层次绑定的事件

以下面的HTML页面为例:

<!DOCTYPE html>
<html>
  <head>
    <title>Event Bubling Example</title>
  </head>
  <body>
    <div id="myDiv">Click Me</div>
  </body>
</html>

当点击了<div>元素,click事件会按照如下顺序传播

graph LR
div-->body
body-->html
html-->document

事件从我们单击的元素开始,沿着DOM树向上传播,在每一级节点上都会发生,直至传播到document对象

image

事件捕获

Netscape提出的另一种事件流是事件捕获(event capturing),即不太具体的节点应该更早接收到事件,最具体的节点应该最后接收到事件。事件捕获的用于在于事件到达预订目标之前捕获它。

仍然以上面的页面作为例子,在事件捕获的状态下,点击<div>元素,click事件会按照相反的顺序传播:

graph LR
document-->html
html-->body
body-->div

事件捕获过程中,document对象最先接收到click事件,然后事件沿DOM树向下,直至传播到事件的实际目标,即<div>元素

image

DOM事件流

==DOM2级事件规定的事件流包括了三个阶段==:

  1. 事件捕获阶段
  2. 处于目标阶段
  3. 事件冒泡阶段

首先发生的事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件,最后一个阶段是冒泡阶段,可以在这个阶段对事件作出相应。

以前面的HTML页面为例,单机<div>元素会按照下图顺序触发事件

image

在DOM事件流中,实际的目标<div>元素在捕获阶段不会接收到事件,这意味着在捕获阶段事件从documenthtml再到body后就停止了。下一个阶段是“处于目标”阶段,事件在<div>上发生。然后冒泡阶段发生,事件有传播回document

==在事件处理中,事件处于目标阶段被看成冒泡阶段的一部分。==

==为了最大限度的兼容,大多是情况下都是将事件处理程序添加到事件冒泡阶段。不是特别需要,不建议在事件捕获阶段注册事件处理程序==

DOM2级事件处理程序

DOM2级事件定义了对两个方法:

  1. addEventListener:添加事件处理程序
  2. removeEventListener:删除事件处理程序

所有DOM节点都包括这两个方法,它们都接受3个参数,前两个参数是要处理的事件名和作为事件处理程序的函数。

第三个参数是一个布尔值,如果是true,表示在捕获阶段调用事件处理程序,如果是false,表示在冒泡阶段调用事件处理程序。默认为false

看一个例子:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>事件冒泡</title>
  <style>
    #outer {
      width: 500px;
      height: 300px;
      background: darkcyan;
    }
    #inner {
      width: 300px;
      height: 150px;
      background: chocolate;
    }
  </style>
</head>
<body>
  <div id="outer">Parent
    <div id="inner">Child</div>
  </div>
</body>
<script>
  const outer = document.getElementById('outer'),
    inner = document.getElementById('inner');
  outer.addEventListener('click', function () {
    console.log('parent')
  });
  inner.addEventListener('click', function () {
    console.log('child')
  });
</script>
</html>

image

而点击child,事件在冒泡阶段触发,首先被触发的是child本身,打印了child,然后向上层冒泡,触发parent的事件,打印出parent

addEventListener的第三个参数改变为true,则事件触发由冒泡阶段转为捕获阶段,此时,当点击child,事件在捕获阶段的传播从document开始,向下传播到parent,然后再到child本身,所以会先打印parent,然后才打印出child

当冒泡阶段和捕获阶段都会调用事件处理程序时,事件按DOM事件流的顺序执行时间处理程序

image

当事件处于目标阶段时,事件调用顺序决定于绑定事件的书写顺序。

outer.addEventListener('click', function() {
  console.log('parent冒泡')
}, false);
outer.addEventListener('click', function() {
  console.log('parent捕获')
}, true);
inner.addEventListener('click', function() {
  console.log('child冒泡')
}, false);
inner.addEventListener('click', function() {
  console.log('child捕获')
}, true);

上面的代码,由于child是处于目标阶段的事件,执行的顺序和我们书写的顺序有关,因此是'child冒泡'在前,而'child捕获'在后。而parent的事件就会按照DOM事件流顺序执行,即先'parent捕获''parent冒泡'

因此,这种情况下的调用顺序是:

graph LR
parent捕获-->child冒泡
child冒泡-->child捕获
child捕获-->parent冒泡

如果我们改动一下书写顺序:

outer.addEventListener('click', function() {
  console.log('parent冒泡')
}, false);
outer.addEventListener('click', function() {
  console.log('parent捕获')
}, true);
inner.addEventListener('click', function() {
  console.log('child捕获')
}, true);
inner.addEventListener('click', function() {
  console.log('child冒泡')
}, false);

那么调用顺序就变成了:

graph LR
parent捕获-->child捕获
child捕获-->child冒泡
child冒泡-->parent冒泡

事件委托

事件委托就是把一个元素响应事件的函数委托到父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。

事件委托的优点:

  1. 提高性能
  2. 动态绑定事件(不必再为动态生成的、需要绑定事件的元素逐一绑定事件)

这样的一个例子:鼠标放到li上,让对应的li背景变红:

<body>
  <ul id="ul">
    <li>item1</li>
    <li>item2</li>
    <li>item3</li>
  </ul>
</body>
<script>
  document.querySelector('#ul').addEventListener('mouseover', function (e) {
    if(e.target.tagName.toLowerCase() === 'li') {
      document.querySelectorAll('#ul li').forEach(v => {
        if(v.tagName.toLowerCase() === 'li') {
          v.style.backgroundColor = '#fff'
        }
      });
      e.target.style.backgroundColor = 'red'
    }
  });
  // 上面的forEach遍历也可以通过再添加一个事件处理程序完成
  // document.querySelector('#ul').addEventListener('mouseout', function (e) {
  //   if(e.target.tagName.toLowerCase() === 'li') {
  //     e.target.style.backgroundColor = '#fff'
  //   }
  // })
</script>
</html>

target属性可返回事件的目标节点(触发该事件的节点),如生成事件的元素、文档或窗口。

这样避免了在所有的li上绑定事件,而且如果是动态生成的节点,直接在li上绑定事件就需要再次绑定,而事件委托就不需要。

利用jquery也可以实现:

$("ul").on("click","li",function(){
  // some code here
})

其中on方法的第二个参数是一个jQuery选择器,用于指定哪些后代元素可以触发绑定的事件。如果该参数为null或被省略,则表示当前元素自身绑定事件(实际触发者也可能是后代元素,只要事件流能到达当前元素即可)。

阻止事件冒泡

event.stopPropagation()

可以使用event.stopPropagation()来阻止事件继续传播,在上面的parentchild的例子中,如果将child绑定的事件改为下面的代码:

outer.addEventListener('click', function() {
  console.log('parent冒泡')
}, false);
inner.addEventListener('click', function() {
  console.log('child冒泡');
  e.stopPropagation()
}, false);

这样再次点击child,就不会触发parentclick事件

e.target

也可以通过e.target来阻止事件冒泡

document.body.addEventListener('click', e = > {
  // 通过e.target判断阻止冒泡
  if (e.target && e.target.matches('a')) {
    return;
  }
})

return false

JS的原生的事件绑定中,return false是不能阻止事件冒泡的,它可以用来阻止浏览器的默认事件,作用于event.preventDefault()相同

在jQuery绑定的事件中使用return false,既阻止默认行为又停止冒泡

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容