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事件-捕获-冒泡

  • 相关阅读:
    尽可能的降低重复劳动——模板策略
    一个CSS上中下三行三列结构的Div布局
    程序员那些悲催的事儿
    给NSString增加Java风格的方法
    DIV和table页面布局的区别和联系
    cocos2d播放雪花
    时间复杂度和空间复杂度2 数据结构和算法04
    OD使用教程4 调试篇04|解密系列
    OD使用教程5 调试篇05|解密系列
    PE结构简图
  • 原文地址:https://www.cnblogs.com/weiqinl/p/9460711.html
Copyright © 2011-2022 走看看