【问题标题】:Unable to understand useCapture parameter in addEventListener无法理解 addEventListener 中的 useCapture 参数
【发布时间】:2011-11-15 22:21:27
【问题描述】:

我已阅读https://developer.mozilla.org/en/DOM/element.addEventListener 的文章,但无法理解useCapture 属性。定义有:

如果为真,useCapture 表示用户希望启动捕获。启动捕获后,指定类型的所有事件将被分派到注册的侦听器,然后再分派到 DOM 树中它下方的任何 EventTargets。通过树向上冒泡的事件不会触发指定使用捕获的侦听器。

在这段代码中,父事件在子事件之前触发,所以我无法理解它 behavior.Document 对象的 usecapture 为 true,子 div 的 usecapture 设置为 false,并且遵循 document usecapture。那么为什么 document 属性优先于 child。

function load() {
  document.addEventListener("click", function() {
    alert("parent event");
  }, true);

  document.getElementById("div1").addEventListener("click", function() {
    alert("child event");
  }, false);
}
<body onload="load()">
  <div id="div1">click me</div>
</body>

【问题讨论】:

    标签: javascript dom dom-events


    【解决方案1】:

    捕获事件 (useCapture = true) 与气泡事件 (useCapture = false)

    MDN Reference

    • 捕获事件将在气泡事件之前调度
    • 事件传播顺序是
      1. 父捕获
      2. 儿童抓捕
      3. 目标捕获和目标气泡
        • 按照注册顺序
        • 当元素是事件的目标时,useCapture 参数无关紧要(感谢@bam 和@legend80s)
      4. 儿童泡泡
      5. 父气泡
    • stopPropagation() 将停止流动

    演示

    结果:

    1. 父捕获
    2. 目标气泡 1

      (因为Target的Capture和Bubble会按照注册的顺序触发,所以Bubble事件在Capture事件之前触发)

    3. 目标捕获

    4. 目标泡泡 2
    5. 父气泡

    var parent = document.getElementById('parent'),
    target = document.getElementById('target');
    
    target.addEventListener('click', function (e) { 
    console.log('Target Bubble 1');
    // e.stopPropagation();
    }, false);
    
    target.addEventListener('click', function (e) { 
    console.log('Target Capture');
    // e.stopPropagation();
    }, true);
    
    target.addEventListener('click', function (e) { 
    console.log('Target Bubble 2');
    // e.stopPropagation();
    }, false);
    
    parent.addEventListener('click', function (e) { 
    console.log('Parent Capture');
    // e.stopPropagation();
    }, true);
    
    parent.addEventListener('click', function (e) { 
    console.log('Parent Bubble');
    // e.stopPropagation();
    }, false);
    <div id="parent">
        <button id="target" style="padding: 1em 0.8em;">
            Trigger event
        </button>
    </div>

    【讨论】:

    • 示例中有一个错误:你声明子事件的顺序是:1.子捕获 2.子气泡 重要!只是因为如果 Child 将成为事件的目标,则将以相同的顺序调用侦听器。请参阅 MDN 上的注释:当元素是事件的目标时,'useCapture' 参数无关紧要。 (developer.mozilla.org/en-US/docs/Web/API/EventTarget/…)
    • 注意:对于附加到事件目标的事件监听器,事件处于目标阶段,而不是捕获和冒泡阶段。 Events in the target phase will trigger all listeners on an element in the order they were registered, regardless of the useCapture parameter. 来自developer.mozilla.org/en-US/docs/Web/API/EventTarget/…。所以不存在“儿童捕捉”和“儿童泡泡”阶段。
    • 这就解释了为什么运行示例会在“儿童捕获”之前产生“儿童气泡 1”,而图表表明任何元素都应该始终首先出现“捕获”!
    【解决方案2】:

    事件可以在两种情况下激活:开始时(“捕获”)和结束时(“气泡”)。 事件按照定义的顺序执行。比如说,你定义了 4 个事件监听器:

    window.addEventListener("click", function(){console.log(1)}, false);
    window.addEventListener("click", function(){console.log(2)}, true);
    window.addEventListener("click", function(){console.log(3)}, false);
    window.addEventListener("click", function(){console.log(4)}, true);

    日志消息将按以下顺序显示:

    • 2(首先定义,使用capture=true
    • 4(使用capture=true 定义第二个)
    • 1(第一个用capture=false定义的事件)
    • 3(第二个用capture=false定义的事件)

    【讨论】:

    • 执行顺序为not guaranteed:no specification is made as to the order in which they will receive the event with regards to the other EventListeners on the EventTarget。我还没有测试所有的浏览器,所以他们可能只是碰巧以同样的方式实现它。但是,捕获事件将在非捕获事件之前完成。
    • @tjameson DOM2 规范的后继者DOM3 events 保证了执行顺序:“实现必须确定当前目标的candidate event listeners。这必须是按注册顺序在当前目标上注册的所有事件侦听器的列表。"
    • 所以我猜这基本上与事件顺序有关
    • @slier,是的,同一事件的多个处理程序的执行顺序。
    • 不知道为什么这是公认的答案,因为 afaik,捕获和冒泡谈论传播行为,而不是规定多个相邻事件处理程序的执行顺序
    【解决方案3】:

    总结:

    DOM 规范描述于:

    https://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html#Events-phases

    工作方式如下:

    按照从树的根 (document) 到 目标节点 的路径调度事件。目标节点是最深的HTML元素,即event.target。事件分派(也称为事件传播)分三个阶段进行,顺序如下:

    1. 捕获阶段:事件从树的根 (document) 到目标节点的直接父节点分派给目标的祖先。
    2. 目标阶段:事件被分派到目标节点。目标阶段始终位于最深的 html 元素上,在该元素上调度了事件。
    3. 冒泡阶段:事件从目标节点的直接父节点分派到目标的祖先节点。

    示例:

    // bubbling handlers, third argument (useCapture) false (default)
    document.getElementById('outerBubble').addEventListener('click', () => {
      console.log('outerBubble');
    }, false)
    
    document.getElementById('innerBubble').addEventListener('click', () => {
      console.log('innerBubble');
    }, false)
    
    
    // capturing handlers, third argument (useCapture)  true
    document.getElementById('outerCapture').addEventListener('click', () => {
      console.log('outerCapture');
    }, true)
    
    document.getElementById('innerCapture').addEventListener('click', () => {
      console.log('innerCapture');
    }, true)
    div:hover{
      color: red;
      cursor: pointer;
    }
    <!-- event bubbling -->
    <div id="outerBubble">
      <div id="innerBubble">click me to see Bubbling</div>
    </div>
    
    
    <!-- event capturing -->
    <div id="outerCapture">
      <div id="innerCapture">click me to see Capturing</div>
    </div>

    上面的例子真正说明了事件冒泡和事件捕获之间的区别。使用addEventListener 添加事件侦听器时,还有第三个元素称为useCapture。这是一个boolean,当设置为true 时,允许事件侦听器使用事件捕获而不是事件冒泡。

    在我们的示例中,当我们将 useCapture 参数设置为 false 时,我们看到发生了事件冒泡。首先触发目标阶段的事件(记录innerBubble),然后通过事件冒泡触发父元素中的事件(记录outerBubble)。

    当我们将 useCapture 参数设置为 true 时,我们看到外部 &lt;div&gt; 中的事件首先被触发。这是因为该事件现在是在捕获阶段而不是冒泡阶段触发的。

    【讨论】:

      【解决方案4】:

      鉴于事件的三个​​阶段旅行

      1. 捕获阶段:事件从树的根到目标的直接父级分派给目标的祖先 节点。
      2. 目标阶段:事件被分派到目标节点。
      3. 冒泡阶段:事件从目标节点的直接父节点分派到目标的祖先节点的根节点 树。

      useCapture 表示事件 travel 将在哪个阶段进行:

      如果trueuseCapture表示用户希望添加事件 仅用于捕获阶段的侦听器,即此事件侦听器不会 在目标和冒泡阶段触发。如果false,则 事件监听器只会在目标和冒泡期间触发 阶段

      来源与第二好的答案相同:https://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html#Events-phases

      【讨论】:

        【解决方案5】:

        我发现此图对于理解捕获/目标/冒泡阶段非常有用: http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html#Events-phases

        下面是从链接中提取的内容。

        阶段

        事件按照从树根到此目标节点的路径进行调度。然后可以在目标节点级别或从树中更高的任何目标的祖先本地处理它。事件分派(也称为事件传播)分三个阶段进行,顺序如下:

        1. 捕获阶段:事件被分派给目标的祖先 从树的根到目标节点的直接父节点。
        2. 目标阶段:事件被分派到目标节点。
        3. 冒泡阶段:事件被分派到目标的 从目标节点的直接父节点到根节点的祖先 树。

        目标的祖先是在事件的初始调度之前确定的。如果在调度过程中删除了目标节点,或者添加或删除了目标的祖先,则事件传播将始终基于调度之前确定的目标节点和目标的祖先。

        某些事件可能不一定完成 DOM 事件流的三个阶段,例如事件只能定义为一个或两个阶段。例如,本规范中定义的事件将始终完成捕获和目标阶段,但有些不会完成冒泡阶段(“冒泡事件”与“非冒泡事件”,另请参见 Event.bubbles 属性)。

        【讨论】:

        • 非常漂亮的图表!
        • 目标节点的子节点呢?他们什么时候收到活动?
        • 树的根实际上是Window,而不是document,因为documentWindow的孩子?
        • @Aurimas 他们没有,这没有意义。目标是应该接收事件的最里面的元素。如果你点击 元素(一个空白的地方),那么 内的所有元素(=页面的所有元素)显然不应该收到点击事件。
        • 我只希望所有解释“什么”的资源都包含“为什么”。像往常一样进行更多的谷歌搜索。
        【解决方案6】:

        代码示例:

        <div id="div1" style="background:#9595FF">
          Outer Div<br />
          <div id="div2" style="background:#FFFFFF">
            Inner Div
          </div>
        </div>
        

        Javascript 代码:

        d1 = document.getElementById("div1");
        d2 = document.getElementById("div2");
        

        如果两者都设置为 false

        d1.addEventListener('click',function(){alert("Div 1")},false);
        d2.addEventListener('click',function(){alert("Div 2")},false);
        

        Executes: Onclicking Inner Div,警报显示为: 第 2 部分 > 第 1 部分

        这里的脚本是从内部元素执行的:事件冒泡(useCapture 已设置为 false)

        div 1 设置为 true,div 2 设置为 false

        d1.addEventListener('click',function(){alert("Div 1")},true);
        d2.addEventListener('click',function(){alert("Div 2")},false);
        

        Executes: Onclicking Inner Div,警报显示为: 第 1 部分 > 第 2 部分

        这里的脚本是从祖先/外部元素执行的:事件捕获(useCapture 已设置为 true)

        div 1 设置为 false,div 2 设置为 true

        d1.addEventListener('click',function(){alert("Div 1")},false);
        d2.addEventListener('click',function(){alert("Div 2")},true);
        

        Executes: Onclicking Inner Div,警报显示为: 第 2 部分 > 第 1 部分

        这里的脚本是从内部元素执行的:事件冒泡(useCapture 已设置为 false)

        div 1 设置为 true,div 2 设置为 true

        d1.addEventListener('click',function(){alert("Div 1")},true);
        d2.addEventListener('click',function(){alert("Div 2")},true);
        

        Executes: Onclicking Inner Div,警报显示为: 第 1 部分 > 第 2 部分

        这里的脚本是从祖先/外部元素执行的:Event Capturing since useCapture has been set to true

        【讨论】:

        • 在这种情况下,“大于”人字形是什么意思?
        • 只是一个指示执行顺序的箭头,如“Div 1 first, Div 2 second”
        【解决方案7】:

        仅当项目处于同一级别时,定义顺序才重要。如果您颠倒代码中的定义顺序,您将得到相同的结果。

        但是,如果您反转两个事件处理程序上的 useCapture 设置,则子事件处理程序会在父事件处理程序之前响应。这样做的原因是子事件处理程序现在将在捕获阶段触发,该阶段在触发父事件处理程序的冒泡阶段之前。

        如果您将两个事件处理程序的 useCapture 设置为 true(无论定义顺序如何),将首先触发父事件处理程序,因为它在捕获阶段位于子事件之前。

        相反,如果您将两个事件处理程序的 useCapture 设置为 false(同样无论定义顺序如何),子事件处理程序将首先被触发,因为它在冒泡阶段先于父事件处理程序。

        【讨论】:

          【解决方案8】:

          一切都与事件模型有关:http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-flow 您可以在冒泡阶段或捕获阶段捕获事件。您的选择。
          看看http://www.quirksmode.org/js/events_order.html - 你会发现它非常有用。

          【讨论】:

          • w3 的链接与谷歌搜索一样有用,甚至比谷歌搜索更有用,我无法理解那里的任何内容。
          • 是的,w3 链接只是一大堆词,但与此相反,第二个指向 quirksmode 站点的链接很好且简短地解释了该主题。
          【解决方案9】:

          当你说 useCapture = true 时,事件在捕获阶段从上到下执行,当它为假时,它会从下到上执行气泡。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-25
            • 1970-01-01
            • 2012-12-07
            • 2021-04-25
            • 2019-08-23
            • 1970-01-01
            相关资源
            最近更新 更多