zoukankan      html  css  js  c++  java
  • js事件流机制冒泡和捕获

    JavaScript与HTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。

    事件流

    从页面中接收事件的顺序称为事件流。

    IE --> 事件冒泡流

    Netscape --> 事件捕获流

    查看源码:DOM2事件-捕获-冒泡

    事件冒泡

    IE的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

    我们先来个简单的例子,这是HTML结构

    
    <!DOCTYPE html>
    
    <html lang="en">
    
    <head>
    
      <title>js事件流</title>
    
    </head>
    
    <body>
    
      <div id="div">
    
        我是div
    
      </div>
    
    </body>
    
    </html>
    
    

    有一个div元素。如果我们点击<div>元素,那么这个click事件的顺序会怎么呢?

    我们给几个元素都添加监听事件,

    element.addEventListener(event, function, useCapture)
    参数说明:

    • event 字符串。指定事件名,比如 click、mouseenter、mouseleave
    • function 函数。指定要事件触发时执行的函数。
    • useCapture 布尔值。指定事件是否在捕获或冒泡阶段执行。默认值是false,即事件在冒泡阶段执行。true,在捕获阶段执行
    
    var div = document.getElementById('div')
    
    var body = document.body
    
    var html = document.documentElement
    
    div.addEventListener('click', function () {
    
      console.log('div标签')
    
    }, false)
    
    body.addEventListener('click', function () {
    
      console.log('body ')
    
    },  false)
    
    html.addEventListener('click', function () {
    
      console.log('html')
    
    },  false)
    
    document.addEventListener('click', function () {
    
      console.log('document')
    
    }, false)
    
    

    然后点击<div>元素,查看控制台输出,
    事件冒泡流

    由上面的输出结果可以看出,这个click事件会按照如下顺序传播:
    <div> ---> <body>---> <html> --->document

    也就是说,click事件首先发生在目标元素,然后,click事件沿着DOM树向上传播到document对象。这就是事件冒泡。

    所有现代浏览器都支持事件冒泡。IE9、Firefox、Chrome和Safari则将事件一直冒泡到window对象。

    如果,我们在每个DOM元素上都设置监听事件,会得到的事件的传播顺序是:
    <div> ---><body> ---> <html> --->document ---> window

    事件捕获

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

    我们还是可以看刚才的例子,DOM结构不变,我们修改下监听事件

    
    var div = document.getElementById('div')
    
    var body = document.body
    
    var html = document.documentElement
    
    div.addEventListener('click', function () {
    
      console.log('div标签')
    
    }, true)
    
    body.addEventListener('click', function () {
    
      console.log('body ')
    
    },  true)
    
    html.addEventListener('click', function () {
    
      console.log('html')
    
    },  true)
    
    document.addEventListener('click', function () {
    
      console.log('document')
    
    }, true)
    
    
    
    

    之后,还是点击<div>元素,得到的结果
    事件捕获流

    从上面例子可以看出,事件走向: document ---><html> ---> <body> ---><div>

    也就是说,在事件捕获过程中,document对象首先接收到click事件,然后事件沿着DOM树依次向下,一直传播到事件的实际目标,即<div>元素。这就是事件捕获。与事件冒泡过程,截然相反。

    尽管“DOM2级事件”规范要求事件应该从document对象开始传播,但是现代浏览器大部分都是从window对象开始捕获事件的。
    如果,我们在每个DOM元素上都设置监听事件,会得到的事件的传播顺序是:
    window ---> document ---><html> ---> <body> ---><div>

    由于在老版本的浏览器中不支持,因此事件捕获用的人比较少,除非在特殊需要的时候才使用。

    DOM事件流

    “DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。

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

    DOM2级事件流

    • 事件捕获阶段:在DOM事件流中,实际的目标(<div>元素)在捕获阶段不会接收到事件。这意味着在捕获阶段,事件从document到<html>再到<body>后就停止了。

    • 处于目标阶段:事件在<div>上发生,并在事件处理中被看成冒泡阶段的一部分。

    • 事件冒泡阶段:冒泡阶段发生,事件又传播回文档。

    虽然,“DOM2级事件”规范明确要求捕获阶段不会涉及事件目标,但是IE9及以上的现代浏览器都会在捕获阶段触发事件目标对象上的事件。结果,就是有两个机会在目标对象上面操作事件,也就是说上图中的步骤4,既可以在捕获阶段发生,也可以在冒泡阶段发生。

    IE8级更早版本不支持DOM事件流,现代浏览器都支持DOM事件流。

    事件流的应用

    事件流比较典型应用是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理一类型的所有事件。

    我们查看一个常用例子,这是一个无序列表的DOM结构:

    
      <ul>
    
        <li id="li1">我是第1个li</li>
    
        <li id="li2">我是第2个li</li>
    
        <li id="li3">我是第3个li</li>
    
      </ul>
    
    

    我们的需求是,点击不同列,输出不同的消息。

    • 第一种做法:给每个<li>添加点击事件,这样能分别处理事件,展示不同的内容。
    
    document.getElementById('li1').addEventListener('click', function(e) {
    
        console.log('我是第一个li')
    
    }, false)
    
    document.getElementById('li2').addEventListener('click', function(e) {
    
        console.log('我是第2个li')
    
    }, false)
    
    document.getElementById('li3').addEventListener('click', function(e) {
    
        console.log('我是第3个li')
    
    }, false)
    
    

    单击每一个<li>,会输出对应的内容。

    • 第二种做法:给<li>元素的父元素<ul>添加一个处理事件,
    
    document.querySelector('ul').addEventListener('click', function (e) {
    
      console.log(e.target.innerText)
    
    }, false)
    
    

    单击每一个<li>,会展示不同的<li>中文本元素内容。

    在这段代码中,我们只为<ul>元素添加了一个onclick事件处理程序。由于所有<li>都是<ul>元素的子节点,而且它们的事件会冒泡,所以单击事件最终会被这个函数处理。

    以上两种方式,第二种所具有的优势:

    1. 事前消耗更低。因为只取得了一个DOM元素,只添加了一个事件处理程序。

    2. 占用的内存更少。每个函数都是对象,都会占用内存。

    3. 性能更优。 内存中的对象越多,性能就越差。

    4. 如果以后要增减<li>元素,也不用修改事件方法,可以获取相同的处理结果。

    所以,比较推荐使用第二种方式。

    最适合采用事件委托技术的事件包括clickmousedownmouseupkeydownkeyupkeypress。虽然mouseovermouseout事件也冒泡,但要适当处理它们并不容易,而且经常要计算元素的位置。

    参考资料:

    JavaScript高级程序设计(第三版)- 第13章 事件

    查看源码:DOM2事件-捕获-冒泡

  • 相关阅读:
    383. Ransom Note
    598. Range Addition II
    453. Minimum Moves to Equal Array Elements
    492. Construct the Rectangle
    171. Excel Sheet Column Number
    697. Degree of an Array
    665. Nondecreasing Array
    视频网站使用H265编码能提高视频清晰度吗?
    现阶段的语音视频通话SDK需要解决哪些问题?
    企业远程高清会议平台视频会议系统在手机端使用的必备要求有哪些?
  • 原文地址:https://www.cnblogs.com/weiqinl/p/9460711.html
Copyright © 2011-2022 走看看