JS中的事件流

2023-03-15  本文已影响0人  奋斗_登

1.事件流

在浏览器中,JavaScript和HTML之间的交互是通过事件去实现的,常用的事件有代表鼠标单击的click事件、代表加载的load事件、代表鼠标指针悬浮的mouseover事件。在事件发生时,会相对应地触发绑定在元素上的事件处理程序,以处理对应的操作。
通常一个页面会绑定很多的事件,那么具体的事件触发顺序是什么样的呢?
这就会涉及事件流的概念,事件流描述的是从页面中接收事件的顺序。事件发生后会在目标节点和根节点之间按照特定的顺序传播,路径经过的节点都会接收到事件。我们通过下面的场景来直观地想象一下事件的流转顺序。
页面上有一个div,分别在body、div、p、span上绑定了click事件。假如我在span上执行了单击的操作,那么将会产生什么样的事件流呢?

<body>
    <div>
        <p>
            <span>
                文本一
            </span>
        </p>
    </div>
</body>

第一种事件传递顺序是先触发最外层的body元素,然后向内传播,依次触发div、p与span元素。
第二种事件传递顺序先触发由最内层的span元素,然后向外传播,依次触发p、div与body元素。
第一种事件传递顺序对应的是捕获型事件流,第二种事件传递顺序对应的是冒泡型事件流。
一个完整的事件流实际包含了3个阶段:事件捕获阶段>事件目标阶段>事件冒泡阶段。上述两种类型的事件流实际对应其中的事件捕获阶段与事件冒泡阶段。


事件流.png
let spanDom = document.querySelector('span');
spanDom .addEventListener("click", function (e) {
    console.info(`span trigger target:${e.target.tagName.toLowerCase()} currentTarget:${e.currentTarget.tagName.toLowerCase()} `);
}, false)

let p1Dom = document.querySelector('p');
p1Dom.addEventListener('click', function (e) {
    console.info(`p trigger  target:${e.target.tagName.toLowerCase()} currentTarget:${e.currentTarget.tagName.toLowerCase()} `);
})

let divDom = document.querySelector('div');
divDom.addEventListener('click', function (e) {
    console.info(`div trigger  target:${e.target.tagName.toLowerCase()} currentTarget:${e.currentTarget.tagName.toLowerCase()} `);
})

let bodyDom = document.querySelector('body');
bodyDom.addEventListener('click', function (e) {
    console.info(`body trigger  target:${e.target.tagName.toLowerCase()} currentTarget:${e.currentTarget.tagName.toLowerCase()} `);
})

当点击span标签时候,将输出


span click

使用addEventListener()函数绑定的事件在默认情况下,即第三个参数默认为false时,按照冒泡型事件流处理。第三个参数为true时,按捕获型事件流处理,再次点击span时候会输出如下:


span click
如果有元素绑定了捕获类型事件,则会优先于冒泡类型事件而先执行。

2.事件处理程序

简单理解事件处理程序,就是响应某个事件的函数,例如onclick()函数、onload()函数就是响应单击、加载事件的函数,对应的是一段JavaScript的函数代码。
根据W3C DOM标准,事件处理程序分为DOM0、DOM2、DOM3这3种级别的事件处理程序。由于在DOM1中并没有定义事件的相关内容,因此没有所谓的DOM1级事件处理程序。

var btn = document.getElementById("btn"); 
btn.onclick = function(){}

第二种是直接在html中设置对应事件属性的值,值有两种表现形式,一种是执行的函数体,另一种是函数名,然后在script标签中定义该函数。

<button onclick="alert('hi');">单击</button>
<button onclick="clickFn()">单击</button>
<script>
    function clickFn() {
        alert('hi');
    }
</script>

以上两种DOM0级事件处理程序同时存在时,第一种在JavaScript中定义的事件处理程序会覆盖掉后面在html标签中定义的事件处理程序。
DOM0级事件处理程序只支持事件冒泡阶段,一个事件处理程序只能绑定一个函数。

element.attachEvent("on"+ eventName, handler);         //添加事件处理程序
element.detachEvent("on"+ eventName, handler);         //删除事件处理程序

在IE11及其他非IE浏览器中,同时支持事件捕获和事件冒泡两个阶段,可以通过addEventListener()函数添加事件处理程序,通过removeEventListener()函数删除事件处理程序。

addEventListener(eventName, handler, useCapture);       //添加事件处理程序
removeEventListener(eventName, handler, useCapture);  //删除事件处理程序
//其中的useCapture参数表示是否支持事件捕获,true表示支持事件捕获,false表示支持事件冒泡,默认状态为false。
<body>
<div>
    <p>
            <span>
                自定义事件
            </span>
    </p>
</div>
</body>
<script>
    var customEvent;
    (function () {
        if (document.implementation.hasFeature('CusomEvents', '3.0')) {
            let detailData = {name: 'climber.lee'}
            customEvent = document.createEvent('CustomEvent');
            customEvent.initCustomEvent('myEvent', true, false, detailData)
            let p = document.querySelector('p');
            p.addEventListener('myEvent', function (e) {
                console.info(`p监听到自定义事件执行,参数为:`,e.detail)
            });
            let span = document.querySelector('span');
            span.addEventListener('click', function () {
                p.dispatchEvent(customEvent);
            })
        }
    })();
</script>

点击span后输出


自定义事件

3.事件(Event)对象

事件在浏览器中是以Event对象的形式存在的,每触发一个事件,就会产生一个Event对象。该对象包含所有与事件相关的信息,包括事件的元素、事件的类型及其他与特定事件相关的信息。
在Event对象中有两个属性总是会引起大家的困扰,那就是target属性和currentTarget属性。两者都可以表示事件的目标元素,但是在事件流中两者却有不同的意义。
· target属性在事件目标阶段,理解为真实操作的目标元素。
· currentTarget属性在事件捕获、事件目标、事件冒泡这3个阶段,理解为当前事件流所处的某个阶段对应的目标元素。
可以参考第一部分事件流的实例来理解target和currentTarget的区别
有时我们并不想要事件进行冒泡,可通过stopPropagation和stopImmediatePropagation两个方法阻止冒泡
· stopPropagation()函数仅会阻止事件冒泡,其他事件处理程序仍然可以调用。
· stopImmediatePropagation()函数不仅会阻止冒泡,也会阻止其他事件处理程序的调用。
在众多的HTML标签中,有一些标签是具有默认行为的
· a标签,在单击后默认行为会跳转至href属性指定的链接中。
那么该如何编写代码来阻止元素的默认行为呢?
很简单,就是通过event.preventDefault()函数去实现。

4.事件委托

过多事件处理程序”的解决方案是使用事件委托。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。例如,click事件冒泡到document。这意味着可以为整个页面指定一个onclick事件处理程序,而不用为每个可点击元素分别指定事件处理程序。举个栗子

<ul id="myLinks">
  <li id="goSomewhere">Go somewhere</li>
 <li id="doSomething">Do something</li>
  <li id="sayHi">Say hi</li>
</ul>

这里的HTML包含3个列表项,在被点击时应该执行某个操作。对此,通常的做法是像这样指定3个事件处理程序

let item1 = document.getElementById("goSomewhere");
let item2 = document.getElementById("doSomething");
let item3 = document.getElementById("sayHi");

item1.addEventListener("click", (event) => {
  location.href = "http:// www.wrox.com";
});

item2.addEventListener("click", (event) => {
  document.title = "I changed the document's title";
});

item3.addEventListener("click", (event) => {
  console.log("hi");
});

如果对页面中所有需要使用onclick事件处理程序的元素都如法炮制,结果就会出现大片雷同的只为指定事件处理程序的代码。使用事件委托,只要给所有元素共同的祖先节点添加一个事件处理程序,就可以解决问题。

let list = document.getElementById("myLinks");
list.addEventListener("click", (event) => {
  let target = event.target;

  switch(target.id) {
    case "doSomething":
      document.title = "I changed the document's title";
      break;
    case "goSomewhere":
      location.href = "http:// www.wrox.com";
      break;
    case "sayHi":
      console.log("hi");
      break;
  }
});

事件委托具有如下优点

上一篇 下一篇

猜你喜欢

热点阅读