首页 > 解决方案 > 在 Shadow DOM 中的 setTimeout 中的 event.target 发生了变化?

问题描述

https://jsbin.com/qogewowomi/1/edit?html,js,输出

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>

<body>
  <test-test></test-test>
  <button id='out'>Outside Shadow DOM</button>
</body>

</html>
customElements.define('test-test', class extends HTMLElement {
  constructor() {
    super();

    const node = document.createElement('template');
    node.innerHTML = '<button id="in">Inside Shadow DOM</button>';
    this.attachShadow({
      mode: 'open'
    }).appendChild(node.content);

    this.shadowRoot.querySelector('#in').addEventListener('click', e => {
      console.log(e.target);
      setTimeout(() => {
        console.log(e.target);
      });
    });
  }
});

document.querySelector('#out').addEventListener('click', e => {
  console.log(e.target);
  setTimeout(() => {
    console.log(e.target);
  });
});

我在 shadow DOM 内外的事件侦听器中发现了这些不一致的行为。Inside Shadow DOM单击按钮时,控制台输出:

<button id="in">Inside Shadow DOM</button>
<test-test>...</test-test>

Outside Shadow DOM单击按钮时,控制台输出:

<button id="out">Outside Shadow DOM</button>
<button id="out">Outside Shadow DOM</button>

在 Chrome、FireFox 和 Safari 中测试。他们都有这些不一致的行为。我不知道这是预期的行为还是错误?

更新:这个问题不应该被关闭。另一个没有回答这个问题。

标签: javascriptweb-componentshadow-dom

解决方案


这是预期的行为,而不是错误。

解释它需要太多的字符。

看:


用我简单的话来说:

Javascript是单线程的。
(e)Event是一个在所有事件处理程序中传递的全局对象

当您使用 SetTimeout 时,Event内容可以/将会有所不同

我重写了你的测试代码:https ://jsfiddle.net/CustomElementsExamples/wj1234km/

<shadow-element id="lightcoral" title=One></shadow-element>
<script>
  function log(label, color, scope, evt) {
    let composedTarget = (evt.composed && evt.composedPath());
    console.log(`%c ${label} \t%c ${evt.target.id} `, `background:${color}`, `background:${evt.target.id};color:white`, '\n\ttarget:', evt.target, "\n\tthis:", scope.nodeName || "window", "\n\tcurrentTarget", evt.currentTarget, '\n\tclickedTarget:', evt.clickedTarget, "\n\tcomposed:", evt.composed ? "True" : "False", "composedPath[0]:", composedTarget[0]);
  }

  customElements.define('shadow-element', class extends HTMLElement {
    constructor() {
      super().attachShadow({mode:'open'})
             .innerHTML = `<button id=green>click ${this.title}</button>`;
      let button = this.shadowRoot.querySelector('button');
      button.onclick = e => {
        let savedTarget = e.target;
        e.clickedTarget = e.target;
        button.onclick = false; //prevent double capture
        log(`clicked element:${this.id}`, 'lightgreen', this, e);
        setTimeout(() => {
          log('timeout element', 'red;color:yellow', this, e)
        }, 500);
      };
      //this.onclick = button.onclick;
    }
  });

</script>

输出:

targetnow 是<shadow-element>因为一旦setTimeout运行,全局事件就一直向上传递到 DOM。

currentTarget告诉你所有事件处理完成
https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget

clickedTarget演示您可以在该全局Event 对象(被传递)上设置自定义属性。从而“保存”您单击的目标。但是.. 其他事件(或下面的 element.onclick 函数调用)可能会覆盖它,因此最好在正确的范围内设置自定义变量,并在您的savedTargetsetTimeout


target您可以通过在元素本身上设置单击处理程序来查看更改方式。

target成为<shadow-element>事件冒泡 DOM 并“逃脱”shadowDOM 的那一刻


推荐阅读