事件流

JavaScript中,事件流指的是DOM事件流。

概念

事件的流传过程即DOM事件流。
事件对象在 DOM 中的流传过程,被称为“事件流”。
举个例子:开电脑这个事,首先你是不是得先找到你的电脑,而后找到你的开机键,最初用手按下开机键。实现开电脑这个事件。这整个流程叫做事件流。

DOM事件流

DOM事件,也是有一个流程的。从事件触发开始到事件响应是有三个阶段。

  1. 事件捕捉阶段
  2. 处于指标阶段
  3. 事件冒泡阶段

下面例子中,开电脑这个事件的过程就像JavaScript中的事件流,找开机键这个过程就是 事件捕捉 的过程,你找到开机键后,而后用手按开机键,这个抉择用手去按的过程就是 处于指标阶段 按下开机按钮,电脑开始开机这也就是 事件的冒泡。 程序为先捕捉再冒泡。

理解了事件源,让咱们看看它的三个过程吧!

1.事件捕捉

注:因为事件捕捉不被旧版本浏览器(IE8 及以下)反对,因而理论中通常在冒泡阶段触发事件处理程序。

事件捕捉处于事件流的第一步,
DOM事件触发时(被触发DOM事件的这个元素被叫作事件源),浏览器会从根节点开始 由外到内 进行事件流传。即事件从文档的根节点流向指标对象节点。途中通过各个档次的DOM节点,最终到指标节点,实现事件捕捉。

2.指标阶段

当事件达到指标节点的,事件就进入了指标阶段。事件在指标节点上被触发。
就是事件流传到触发事件的最底层元素上。

3.事件冒泡

事件冒泡与事件捕捉程序相同。事件捕捉的程序是从外到内,事件冒泡是从内到外。
当事件流传到了指标阶段后,处于指标阶段的元素就会将接管到的工夫向上流传,就是顺着事件捕捉的门路,反着流传一次,逐级的向上流传到该元素的先人元素。直到window对象。

看一个例子,点击 box3 会将 box2 与 box1 的点击事件触发。

<!DOCTYPE html><html><head>    <meta charset="UTF-8">    <title>JavaScript 事件冒泡</title></head><style type="text/css">    #box1 { background: blueviolet;}    #box2 {background: aquamarine;}    #box3 {background: tomato;}    div { padding: 40px; margin: auto;}</style><body>    <div id="box1">        <div id="box2">            <div id="box3"></div>        </div>    </div>    <script>        window.onload = function () {            const box1 = document.getElementById('box1')            const box2 = document.getElementById('box2')            const box3 = document.getElementById('box3')            box1.onclick = sayBox1;            box2.onclick = sayBox2;            box3.onclick = sayBox3;            function sayBox3() {                console.log('你点了最外面的box');            }            function sayBox2() {                console.log('你点了最两头的box');            }            function sayBox1() {                console.log('你点了最里面的box');            }        }    </script></body></html>
这个时候 click 捕捉的流传程序为:
window -> document -> \<html> -> \<body> -> <div #box1> -> <div #box2> -> <div #box3>
这个时候 click 冒泡的流传程序为:
\<div #box3> -> \<div #box2> -> \<div #box1> -> \<body> -> \<html> -> document -> window

古代浏览器都是从 window 对象开始捕捉事件的,冒泡最初一站也是 window 对象。而 IE8 及以下浏览器,只会冒泡到 document 对象。
事件冒泡:是由元素的 HTML 构造决定,而不是由元素在页面中的地位决定,所以即使应用定位或浮动使元素脱离父元素的范畴,单击元素时,其仍然存在冒泡景象。

当初咱们晓得了事件流的三个阶段后,那咱们能够利用这个个性做什么呢?

事件委托

构想这样一个场景,当你有一堆的<li>标签在一个<ul>标签下,须要给所有的<li>标签绑定onclick事件,这个问题咱们能够用循环解决,但还有没有更简便的形式呢?
咱们能够给这些<li>独特的父元素<ul>增加onclick事件,那么外面的任何一个<li>标签触发onclick事件时,都会通过冒泡机制,将onclick事件流传到<ul>上,进行解决。这个行为叫做事件委托,<li>利用事件冒泡将事件委托到<ul>上。
也能够利用事件捕捉进行事件委托。用法是一样的,只是程序反了。

  <ul id="myUl">    <li>item 1</li>    <li>item 2</li>    <li>item 3</li>    ...  </ul>

可能还是有点不好了解,简略来说,就是利用事件冒泡,将某个元素上的事件委托给他的父级。

举个生存中的例子,双十一快递到了,须要快递小哥送快递个别是挨家挨户送货上门,这样效率慢,小哥想了个方法,把一个小区的快递都放在小区外面的快递驿站,进行送快递的事件委托,小区的收件人能通过取件码去快递驿站支付到本人的快递。
在这里,快递小哥送快递就是一个事件,收件人就是响应事件的元素,驿站就相当于代理元素,收件人凭着播种码去驿站外面领快递就是事件执行中,代理元素判断以后响应的事件匹配该触发的具体事件。

可是这样做有什么益处呢?

事件委托的长处

事件委托有两个益处

  1. 缩小内存耗费
  2. 动静绑定事件
  3. 缩小内存耗费,优化页面性能

在JavaScript中,每个事件处理程序都是对象,是对象就会占用页面内存,内存中的对象越多,页面的性能当然越差,而且DOM的操作是会导致浏览器对页面进行重排和重绘(这个不分明的话,小伙伴能够理解页面的渲染过程),过多的DOM操作会影响页面的性能。性能优化次要思维之一就是为了最小化的重排和重绘也就是缩小DOM操作。

在下面给<li>标签绑定onclick事件的例子中,应用事件委托就能够不必给每一个<li>绑定一个函数,只须要给<ul>绑定一次就能够了,当li的数量很多时,无疑能缩小大量的内存耗费,节约效率。

  1. 动静绑定事件

如果子元素不确定或者动静生成,能够通过监听父元素来取代监听子元素。
还是下面在<li>标签绑定onclick事件的例子中, 很多时候咱们的这些<li>标签的数量并不是固定的,会依据用户的操作对一些<li>标签进行增删操作。在每次减少或删除标签都要从新对新增或删除元素绑定或解绑对应事件。

能够应用事件委托就能够不必给每一个<li>都要操作一遍,只须要给<ul>绑定一次就能够了,因为事件是绑定在<ul>上的,<li>元素是影响不到<ul>的 ,执行到<li>元素是在真正响应执行事件函数的过程中去匹配的,所以应用事件委托在动静绑定事件的状况下是能够缩小很多反复工作的。

咱们晓得了事件委托的长处,那么该如何应用呢?

事件委托的应用

事件委托的应用须要用的addEventListener()办法,事件监听。
办法将指定的监听器注册到调用该函数的对象上,当该对象触发指定的事件时,指定的回调函数就会被执行。

  • 用法

    element.addEventListener(eventType, function, useCapture);
  • 参数形容
参数必/选填形容
eventType必填指定事件的类型。
function必填指定事件触发后的回调函数。
useCapture选填指定事件是在捕捉阶段执行还是在冒泡阶段执行。

第三个参数 useCapture 是个布尔类型,默认值为false

  • true - 示意事件在捕捉阶段执行执行
  • false- 示意事件在冒泡阶段执行执行

看上面例子

<!DOCTYPE html><html><head>  <meta charset="UTF-8">  <title>JavaScript 事件委托</title></head><body>  <ul>    <li>item 1</li>    <li>item 2</li>    <li>item 3</li>    <li>item 4</li>  </ul>  <script>    const myUl = document.getElementsByTagName("ul")[0];    myUl.addEventListener("click", myUlFn);    function myUlFn(e) {      if (e.target.tagName.toLowerCase() === 'li') { // 判断是否为所须要点击的元素        console.log(`您点击了${e.target.innerText}`);      }    }  </script></body></html>

⚠️ 这是个别的事件委托办法,然而这种写法有问题,就是当_<li>_中还有子元素时,点击这个子元素就不会进行触发事件。这个问题是一个坑。

事件冒泡有时候的确很有用,然而有时候也讨人烦,当你不须要它的时候能不能取消掉呢?

禁止事件冒泡与捕捉

⚠️ 并不是所有事件都会冒泡,比方focus,blur,change,submit,reset,select等。

禁止冒泡和捕捉能够用到办法stopPropagation()
stopPropagation()起到阻止捕捉和冒泡阶段中以后事件的进一步流传。
这是阻止事件的冒泡办法,进行冒泡,然而默认事件任然会执行,当你调用了这个办法后。
如果点击一个a标签,这个a标签会进行跳转。

应用起来也很简略,没有返回值也没有参数。

event.stopPropagation();

请看上面例子,这个例子切实上文事件冒泡例子根底上稍加批改失去的

    <div id="box1">        <div id="box2">            <div id="box3"></div>        </div>    </div>    <script>        const box1 = document.getElementById('box1')        const box2 = document.getElementById('box2')        const box3 = document.getElementById('box3')        box1.onclick = sayBox1;        box2.onclick = sayBox2;        box3.onclick = sayBox3;        function sayBox3() {            console.log('你点了最外面的box');        }        function sayBox2(e) {            console.log('你点了最两头的box');            e.stopPropagation(); //禁止事件捕捉和冒泡        }        function sayBox1() {            console.log('你点了最里面的box');        }    </script>

当事件冒泡到box2时调用了在函数sayBox2,调用了e.stopPropagation(); 就进行冒泡了。

参考文献

MDN中文版 https://developer.mozilla.org/zh-CN/
知乎 https://zhuanlan.zhihu.com/p/26536815