`$().clone(true)` 中断 chrome 扩展中的事件处理程序(适用于 jsbin)

`$().clone(true)` breaks event handlers in chrome extension (works on jsbin)

我正在开发一个 chrome 扩展,它可能会改变数千个 DOM 元素。出于性能原因,我想在内存中执行此操作而不是反复触摸 DOM。 jQuery's clone 非常适用于此,如您在以下代码段中所见。

$(document).ready(function() {
  $("#click-me").on("click", () => {
    $("#click-count").html((idx, num) => ++num)
  })

  $("#with-cloning").click(function() {
    withCloning(($elem) => {
      $elem.find("h1").text("changed with cloning (still works)")
    })
  })

  $("#without-cloning").click(function() {
    withoutCloning(($elem) => {
      $elem.find("h1").text("changed without cloning (still works)")
    })
  })
})

function withCloning(mutateDom) {
  var $elem = $("body")
  // The `true` arg keeps bindings
  var $cloned = $elem.clone(true)
  mutateDom($cloned)
  $elem.replaceWith($cloned)
}

function withoutCloning(mutateDom) {
  mutateDom($("body"))
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <div id="wrapper">
    <h1 id="click-me">Click me!</h1>
    <p>Click count: <span id="click-count">0</span></p>
  </div>
  
  <button id="without-cloning">replace without cloning</button>
  <button id="with-cloning">replace with cloning</button>


<script src="https://code.jquery.com/jquery-3.1.0.js"></script>

</body>
</html>

当我在我的 chrome 扩展中使用这个完全相同的函数时,它成功地替换了内容,但是 破坏了所有事件处理程序

以下功能在我的 chrome 扩展中嵌入网站(和代码段) 时都能完美运行:

function withoutCloning(mutateDom) {
  mutateDom($("body"))
}

如何使 withCloning 函数在 chrome 扩展中正确运行(如代码片段中所示)?


补充说明

您正在做的是预期 破坏事件处理程序。您 不能 克隆事件处理程序,除了通过 jQuery 方法添加的事件,使用 jQuery 方法克隆或替换。因此,用克隆副本替换 DOM 的内容将导致删除事件处理程序。

jQuery .clone() 无法克隆非jQuery 事件

如果没有通过 jQuery 方法添加事件侦听器,

jQuery 无法知道元素上有哪些事件。因此,任何通过正常 DOM 方法(即 .addEventListener())添加的事件将不会被 jQuery 的 .clone().

克隆

这是一个使用 addEventListener() 添加计数事件处理程序的示例。您可以看到 jQuery .clone() 没有克隆它,但是克隆了 jQuery 事件(还有 available on JS Bin):

$(document).ready(function() {
  document.querySelector("#click-me").addEventListener("click", () => {
    $("#click-count").html((idx, num) => ++num)
  })

  $("#with-cloning").click(function() {
    withCloning(($elem) => {
      $elem.find("h1").text("changed with cloning (counting does not work)")
    })
  })

  $("#without-cloning").click(function() {
    withoutCloning(($elem) => {
      $elem.find("h1").text("changed without cloning (still works)")
    })
  })
})

function withCloning(mutateDom) {
  var $elem = $("body")
  // The `true` arg keeps bindings
  var $cloned = $elem.clone(true)
  mutateDom($cloned)
  $elem.replaceWith($cloned)
}

function withoutCloning(mutateDom) {
  mutateDom($("body"))
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <div id="wrapper">
    <h1 id="click-me">Click me!</h1>
    <p>Click count: <span id="click-count">0</span></p>
  </div>
  
  <button id="without-cloning">replace without cloning</button>
  <button id="with-cloning">replace with cloning</button>


<script src="https://code.jquery.com/jquery-3.1.0.js"></script>

</body>
</html>

jQuery 可以克隆通过 jQuery 方法添加的事件

这在您的 JS Bin 中起作用的原因是您使用 jQuery 添加了所有事件处理程序。 JQuery,有效地记录了您通过 jQuery 添加的事件处理程序 并且能够克隆 jQuery-added-events当您 .clone() 元素时与元素一起,或使用 .replaceWith() 替换元素。 jQuery 事件处理程序的恢复发生在 both .clone() and .replaceWith(). In other words, even if perform one of the two operations with the normal JavaScript methods (node.cloneNode() or Node.replaceChild()), but the other with jQuery's method (.clone() and .replaceWith()), jQuery will restore the events which you added using jQuery. You can use the following snippet to play around with which combinations of jQuery vs vanilla JavaScript which are used for the clone and to replace the element (also on JS Bin):

$(document).ready(function() {
  $("#click-me").on("click", () => {
    $("#click-count").html((idx, num) => ++num);
  });

  $("#with-cloning").click(function() {
    withCloning(($elem) => {
      $elem.find("h1").text("changed with cloning (still works)");
    });
  });

  $("#without-cloning").click(function() {
    withoutCloning(($elem) => {
      $elem.find("h1").text("changed without cloning (still works)");
    });
  });
});

function withCloning(mutateDom) {
  var $elem = $("body")
  var used='Used:';
  var $cloned;
  if($('#useClone').is(':checked')){
    //jQuery clone. The `true` arg keeps bindings
    $cloned = $elem.clone(true);
    used += ' clone()';
  } else {
    //Vanilla JavaScript cloneNode. `true` makes a deep copy
    $cloned = $($elem[0].cloneNode(true));
    used += ' cloneNode()';
  }
  mutateDom($cloned);
  if($('#useReplaceWith').is(':checked')){
    //jQuery replaceWith
    $elem.replaceWith($cloned);
    used += ' and replaceWith()';
  } else {
    //Vanilla JavaScript replaceChild
    $elem[0].parentNode.replaceChild($cloned[0],$elem[0]);
    used += ' and replaceChild()';
  }
  console.log(used);
}

function withoutCloning(mutateDom) {
  mutateDom($("body"));
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <div id="wrapper">
    <h1 id="click-me">Click me!</h1>
    <p>Click count: <span id="click-count">0</span></p>
  </div>
  
  <button id="without-cloning">replace without cloning</button>
  <button id="with-cloning">replace with cloning</button></br></br>
  Use:</br>
  <table>
    <tr><td>jQuery</td><td>vanilla JavaScript</td></tr>
    <tr>
      <td><input name="cloneType" type="radio" id="useClone" checked="true">`.clone(true)`</input></td>
      <td><input name="cloneType" type="radio" id="useCloneNode">`.cloneNode(true)`</input></td>
    </tr>
    <tr>
      <td><input name="replaceType" type="radio" id="useReplaceWith" checked="true">`.replaceWith()`</input></td>
      <td><input name="replaceType" type="radio" id="usereplaceChild">`.replaceChild()`</input></td>
    </tr>
  </table>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>

</body>
</html>

请注意,一旦您使用 .cloneNode() 进行了克隆,如果您再次尝试使用 .clone(true) 进行克隆,则不会克隆事件。