javascript中的stoppropagation与stopimmediatepropagation

One of my colleagues at work has started to implement a chat application as a third-party library of Chrome Extensions for a video streaming service. She’s been stuck in many issues that have dragged the rest of us into them, though. I’ve brought the one I think is good enough to share with other JavaScript developers.

我的一位同事已经开始将聊天应用程序实现为Chrome扩展程序的第三方库,以用于视频流服务。 不过,她陷入了许多困扰我们其他人的问题。 我带来了一个我认为足以与其他JavaScript开发人员共享的脚本。

In this article, I’m going to talk about the two methods in the EventTarget object, stopPropagation and stopImmediatePropagation, that many of you know but don’t know how to use properly.

在本文中,我将讨论EventTarget对象中的两个方法stopPropagationstopImmediatePropagation ,你们中的许多人都知道但不知道如何正确使用。

免责声明 (Disclaimer)

This article only deals with Vanilla JavaScript, not with using React or any other JavaScript frameworks. Thus the implementation of events may work differently elsewhere, as React works in its own way, for example, with the event system called SyntheticEvent system.

本文仅涉及Vanilla JavaScript,而不涉及使用React或任何其他JavaScript框架。 因此,事件的实现在其他地方可能会有所不同,因为React以其自己的方式工作,例如,使用称为SyntheticEvent系统的事件系统。

Plus, you should be aware of what event bubbling and capturing is to understand this article better.

另外,您应该知道什么是事件冒泡和捕获是为了更好地理解本文。

什么是element.addEventListener? (What Is element.addEventListener?)

I’m pretty sure you know what this is and how this works if you are a JavaScript developer, but let’s make sure that every one of us knows the same thing.

如果您是JavaScript开发人员,我很确定您知道这是什么以及它如何工作,但是请确保我们每个人都知道同一件事。

When JavaScript adds an event, it works as below under the hood.

当JavaScript添加事件时,它的工作原理如下。

var event_listener_list = [];
var sayHi = function() {
  console.log('hi');
};
window.addEventListener('click', sayHi);
--------- then works like this ---------
event_listener_list.push({
  type: 'click',
  callback: sayHi,
  capture: false, 
  passive: false,
  once: false,
  removed: false
});

It sets a new event to the event handle list only if the type, the callback, the option that contains it should be capturing (or something like that) is identical. Otherwise, it ignores the adding request from the context.

仅当类型,回调,包含它的选项应该被捕获(或类似的东西)相同时,才将新事件设置到事件句柄列表。 否则,它将忽略上下文中的添加请求。

What if you add several different events in a row?

如果您连续添加几个不同的事件怎么办?

var log = function(x) {
  console.log(x);
};
window.addEventListener('click', () => log(1));
window.addEventListener('keydown', () => log(2));
window.addEventListener('dbclick', () => log(3));

The above code is registering the three different event types to the window object, but the internal event listener is keeping the event as follows.

上面的代码将三种不同的事件类型注册到window对象,但是内部事件侦听器将事件保留如下。

[event_listener_list]
index type callback ...
0 click (anonymous)
1 keydown (anonymous)
2 dbclick (anonymous)

When you double-click the window, it runs the callback for the double-click without giving you any error. What the event listener list has inside looks a little messed up, but it’s okay: It finds the right one for you by looking it up from the first index.

双击窗口时,它将运行双击回调,而不会给您任何错误。 事件侦听器列表中包含的内容看起来有些混乱,但这没关系:它可以通过从第一个索引中查找找到适合您的对象。

Then how about this?

那这个呢

window.addEventListener('click', () => log(1));
window.addEventListener('click', () => log(2));
window.addEventListener('click', () => log(3));

What do you think it would show you? Correct, it shows you 1, 2, and 3. They look the same, but each callback is an anonymous function so JavaScript thinks they are different.

您认为它将向您显示什么? 正确,它向您显示1、2和3。它们看起来相同,但是每个回调都是一个匿名函数,因此JavaScript认为它们是不同的。

var i = 1;
function log() {
  console.log(i++);
}
window.addEventListener('click', log);
window.addEventListener('click', log);
window.addEventListener('click', log);

However, this code does not print logs three times because the callback is not anonymous. It has the memory address, thus JavaScript thinks they all are the same request, so it only accepts the first one and ignores the others.

但是,此代码不会打印三次日志,因为回调不是匿名的。 它具有内存地址,因此JavaScript认为它们都是相同的请求,因此它仅接受第一个请求,而忽略其他请求。

什么是stopPropagation? (What Is stopPropagation?)

Before jumping into the stopImmediatePropagation, let’s examine what stopPropagation is. In the JavaScript event system, there are two event phases — bubbling and capturing.

在进入stopImmediatePropagation之前,让我们检查一下stopPropagation是什么。 在JavaScript事件系统中,有两个事件阶段-冒泡和捕获。

Image for post
W3C W3C

Not every DOM event is supportive of bubbling and capturing, but most of them are, though basically, the DOM event starts with the topmost element — the window object. Wherever you click the element on the webpage, the DOM elements always fire the event from the window object as long as the event is servable at the capturing step.

并非每个DOM事件都支持冒泡和捕获,但是从本质上讲,大多数DOM事件都从最顶层的元素(窗口对象)开始。 只要您在网页上单击该元素,则DOM元素始终会触发窗口对象中的事件,只要该事件在捕获步骤中是可服务的即可。

Then it goes all the way down to the actual element where the click event was actually fired. That element is called thetarget event. The callback is executed at this time. And it goes all the way up again to the root element by visiting every ancestor.

然后,它一直下降到实际触发click事件的实际元素。 该元素称为target事件。 回调在此时执行。 通过拜访每个祖先,它又一直上升到根元素。

window.addEventListener('click', log);

JavaScript makes the callback not able to be found at the capturing phase by default if you add the event, as in the above example. To see it at the down-tour step, you should pass the third parameter to the addEventListener.

如上例所示,如果添加事件,则默认情况下,JavaScript使在捕获阶段无法找到回调。 要在下一个步骤中看到它,您应该将第三个参数传递给addEventListener

window.addEventListener('click', log, true);
// or
window.addEventListener('click', log, {
capture: true
});

But remember: Adding the callback, which is able to catch the event at the capturing step, doesn’t mean that it will be executed ahead of the other callbacks that have already been registered. That’s because JavaScript manages the events in the event listener list, which is an array-like type object, and pulls out and calls the event handlers in the order they came in. Of course, event handlers with capture: true are executed first before the other handlers with capture: false.

但是请记住:添加能够在捕获步骤中捕获事件的回调并不意味着它会在已注册的其他回调之前执行。 这是因为JavaScript管理事件侦听器列表中的事件,该事件侦听器列表是一个类似数组的类型对象,并按它们进入的顺序拉出并调用事件处理程序。当然,带有capture: true事件处理程序会在其他具有capture: false处理程序capture: false

The importance of this concept is to be seen when you want to cut off the bridge that lets the event go down or up after the current stage.

当您想切断当前阶段之后事件发生的桥梁时,可以看到此概念的重要性。

Be aware that target is different from currentTarget. target is the actual DOM element your event listener is attached to, while currentTarget, on the other hand, is the DOM element that caught the event listener at the moment. For example, if you click the bottommost element, the target is always the element itself, but currentTarget is changed while the event is bubbled up to the topmost element.

请注意, targetcurrentTarget不同。 target是事件侦听器所附加的实际DOM元素,而currentTarget是此时捕获事件侦听器的DOM元素。 例如,如果单击最底端的元素,则target始终始终是元素本身,但是当事件冒泡到最顶端的元素时, currentTarget会更改。

使用stopPropagation时应注意什么? (What Should You Be Careful of When Using stopPropagation?)

Whenever you don’t want to pass the turn for running the event callback, it’s the right time to use stopPropagation.

每当您不想通过转弯来运行事件回调时,就该使用stopPropagation

There are so many good examples of stopPropagation that I’ll try more to focus on what you should be careful of when you use stopPropagation. Imagine you write JavaScript code and HTML markups in one file as follows.

stopPropagation有很多很好的例子,我将尝试更多地关注于使用stopPropagation时应注意的事项。 假设您在一个文件中编写JavaScript代码和HTML标记,如下所示。

<div id="parent" onclick="log('parent')">
  <div id="child">
  </div>
</div>
<script>
  function log(x) { 
    console.log(x); 
  }
</script>

And there’s a function directly attached to the element. Now it’s treated as an old-school skill, but it stills works fine. And if you add another callback to the parent object using addEventListener, you will never be able to prevent the inline callback from being executed by the flow.

并且有一个函数直接附加到元素上。 现在,它已被视为一种古老的技能,但仍然可以正常工作。 而且,如果使用addEventListener将另一个回调添加到parent对象,则将永远无法阻止流执行内联回调。

<div id="parent" onclick="log('parent')"></div>
<script>
  const parent = document.getElementById("parent");
  parent.addEventListener('click', function() {
    log("parent in addEventListener");
  });
</script>

The only ways that the parent object’s inline callback doesn’t get called are either to get rid of it from the element or to fire another element that is either a parent or a child.

父对象的内联回调不被调用的唯一方法是从元素中摆脱它,或者触发另一个父元素或子元素。

Image for post
Image source: Author
图片来源:作者

When the parent element is clicked, the callback of the “app” element is fired at first at the capturing stage because one of its callback has capture: true. And then it doesn’t call stopPropagation inside, and the turn goes to the “parent” element. The “parent” element has the inline callback, so it will be executed first, printing “parent inline.” And then the callback with capture: false is executed ahead of the one with capture: true, which might seem weird.

单击父元素时,在捕获阶段首先触发“ app”元素的回调,因为其回调之一具有capture: true 。 然后,它不会在内部调用stopPropagation ,而是转到“父”元素。 “ parent”元素具有内联回调,因此将首先执行,并打印“ parent inline”。 然后用回调capture: false被提前一个与执行capture: true ,这似乎不可思议。

Note that the capturing or bubbling stage means that it caught the event at a different DOM level. If you click the element and it has two callbacks with capture: true and capture: false, those events will be executed in the order they were registered.

请注意,捕获或冒泡阶段意味着它在不同的DOM级别捕获了事件。 如果您单击该元素,并且该元素具有两个带有capture: true回调capture: truecapture: false ,则这些事件将按照注册时的顺序执行。

And the last callback of “parent” calls stopPropagation function, so no more events bubble upward.

最后一个“父”回调将调用stopPropagation函数,因此不再有事件冒泡。

什么是stopImmediatePropagation? (What Is stopImmediatePropagation?)

All right, now I’m quite confident that what I know about addEventListener is at least within your own knowledge of it. Now let’s talk about the next topic, stopImmediatePropagation.

好吧,现在我非常有信心,我对addEventListener了解至少是在您自己的知识范围之内。 现在让我们讨论下一个主题stopImmediatePropagation

window.addEventListener('click', () => log(1));
window.addEventListener('click', () => log(2));
window.addEventListener('click', () => log(3));// print 1 2 3

The window object has three event listener callbacks which print 1, 2, and 3. But I don’t want the last one to be executed. How can I do that?

窗口对象具有三个事件侦听器回调,分别显示1、2和3。但是我不希望执行最后一个。 我怎样才能做到这一点?

stopImmediatePropagation prevents JavaScript from tossing the turn to the next callback to be invoked.

stopImmediatePropagation阻止JavaScript将转弯stopImmediatePropagation要调用的下一个回调。

However, remember that stopImmediatePropagation, once it finishes running, not only stops propagating the event to the other events at the same level but also not to the other events at the lower level as well. It is like a little self-centered boy who only cares about himself.

但是,请记住stopImmediatePropagation一旦完成运行,不仅会停止将事件传播到同一级别的其他事件,而且也不会停止传播到较低级别的其他事件。 就像一个以自我为中心的小男孩,只关心自己。

Once the callback finishes running, it entirely finishes the event flow.

回调完成运行后,它将完全完成事件流。

window.addEventListener('click', () => log(1));
window.addEventListener('click', (e) => {
  e.stopImmediatePropagation();
  log(2);
});
window.addEventListener('click', () => log(3));
// print 1 2
Image for post
Image source: Author
图片来源:作者

If you call stopImmediatePropagation at the current element’s event phase, it does not let the context be passed to the next waiting one.

如果在当前元素的事件阶段调用stopImmediatePropagation ,则不会让上下文传递到下一个等待的上下文。

什么时候应该使用stopImmediatePropagation? (When Should You Use stopImmediatePropagation?)

Normally, I guess you might not even need to consider if you should call stopImmediatePropagation because most of the time, you wouldn’t want to block the event propagation flow at the same event level.

通常,我想您可能甚至不需要考虑是否应该调用stopImmediatePropagation因为在大多数情况下,您不想在同一事件级别阻止事件传播流。

Image for post
Image source: Author
图片来源:作者

Imagine component A already set the event listener for a click to the window object — window.addEventListener('click', cb_A) .

想象一下,组件A已经设置了单击窗口对象的事件监听器— window.addEventListener('click', cb_A)

But you’re working on component B at the moment, and you also registered the callback to the window object — window.addEventListener('click', cb_B). But cb_B is always called after cb_A is called because the event listener list holds the event handlers in the order they came in. The workaround for this is to place the callback of component B at the capturing phase.

但是,您现在正在处理组件B,并且还将回调注册到了window对象— window.addEventListener('click', cb_B) 。 但cb_B之后总是叫cb_A被调用,因为事件侦听器列表保存事件处理程序中,他们进来的顺序。这种情况的解决方法是将组分B的回调在捕获阶段。

window.addEventListener('click', cb_B, true);
// or
window.addEventListener('click', cb_B, {
capture: true
});

Since the capturing option is false by default, cb_A for component A is at the bubbling phase. What that does is to put waiting for the context turn in a more front seat of the line, and after then, calling stopImmediatePropagation.

由于默认情况下捕获选项为falsecb_A组件A的cb_A处于冒泡阶段。 这样做是将等待上下文置于该行的更前面,然后再调用stopImmediatePropagation

Image source: Author
图片来源:作者

Here’s the demo of stopImmediatePropagation. They both registered the event listener to the window object and they both are called. If you let component B call stopImmediatePropagation inside the callback that is set to capture: true, it won’t allow the next callback at the next capture phase or the bubble phase to be executed.

这是stopImmediatePropagation的演示。 他们都将事件侦听器注册到window对象,并且都被调用。 如果让组件B在设置为capture: true的回调内调用stopImmediatePropagation ,则它将不允许在下一个捕获阶段或冒泡阶段执行下一个回调。

Note that this way won’t work if the event handlers are registered in the DOM element which you actually clicked, not the one in which the parent element of the actual element you click is. Even though you try to control the flow of event propagation by registering it as the capturing one, if there are existing event handlers in the event handler list, they will always be executed first.

请注意,如果事件处理程序是在您实际单击的DOM元素(而不是您单击的实际元素的父元素所在的元素)中注册的,则这种方法将行不通。 即使您尝试通过将事件注册为捕获事件来控制事件传播的流程,但是如果事件处理程序列表中存在现有的事件处理程序,则始终将它们首先执行。

结论 (Conclusion)

stopPropagation and stopImmediatePropagation are different functional methods you should be careful when using. stopPropagation prevents the event from bubbling up or capturing down to the next elements, and stopImmediatePropgation prevents the event from being executed after the current one finishes running.

stopPropagationstopImmediatePropagation是使用时应注意的不同功能方法。 stopPropagation阻止事件冒泡或捕获到下一个元素,而stopImmediatePropgation阻止事件在当前事件完成运行后执行。

When there’s an event on the element as an inline callback, you can’t stop them calling with stopPropagation. However, if the statement in the inline callback spot is not a function expression (which means it is something like console.log('hello')) instead of calling other functions, it could be stopped by stopPropagation, even though JavaScript wraps it with an anonymous function body. But that’s turned into an old-school thing from years ago, so you might not need to care about it anymore.

当元素上有一个事件作为内联回调时,您无法阻止它们使用stopPropagation调用。 但是,如果内联回调点中的语句不是函数表达式(这意味着它类似于console.log('hello') ),而不是调用其他函数,则即使JavaScript将其包装为,也可以通过stopPropagation停止该语句。匿名函数主体。 但是,从几年前开始,这已变成一种古老的事物,因此您可能不再需要关心它。

And also remember that other JavaScript frameworks such as React may work differently from pure JavaScript because they may have their own event system working.

还要记住,其他JavaScript框架(如React)可能与纯JavaScript有所不同,因为它们可能具有自己的事件系统。

翻译自: https://medium.com/better-programming/stoppropagation-vs-stopimmediatepropagation-in-javascript-27b9f8ce79b5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值