Home  >  Article  >  Web Front-end  >  Understand the event routing bubbling process and delegation mechanism in JavaScript

Understand the event routing bubbling process and delegation mechanism in JavaScript

黄舟
黄舟Original
2017-02-27 14:05:521199browse

When I implement this using pure CSS. I started using JavaScript and style classes to round out the functionality.

Then, I have some ideas, I want to use Delegated Events (event delegation) but I don't want to have any dependencies, plug in any libraries, including jQuery. I need to implement event delegation myself.

Let’s first take a look at what event delegation is? How do they work and how to implement this mechanism.

Okay, what problem does it solve?

Let’s look at a simple example first.

Suppose we have a set of buttons. I click one button at a time, and then I want the clicked state to be set to "active". Cancel active when clicked again.

Then, we can write some HTML:

<ul class="toolbar">
  <li><button class="btn">Pencil</button></li>
  <li><button class="btn">Pen</button></li>
  <li><button class="btn">Eraser</button></li>
</ul>

I can use some standard Javascript events to handle the above logic:

var buttons = document.querySelectorAll(".toolbar .btn");
for(var i = 0; i < buttons.length; i++) {
  var button = buttons[i];
  button.addEventListener("click", function() {
    if(!button.classList.contains("active"))
      button.classList.add("active");
    else
      button.classList.remove("active");
  });
}

It looks good, but it doesn’t actually work like Works as you expect.

The trap of closure

If you have some experience in JavaScript development, this problem will be obvious.

For the uninitiated, the button variable is closed, and the corresponding button will be found every time...but in fact there is only one button here; it will be reassigned every time it loops.

The first loop points to the first button, then the second one. But when you click, the button variable always points to the last button element. This is the problem.

What we need is a stable scope; let’s refactor it.

var buttons = document.querySelectorAll(".toolbar button");
var createToolbarButtonHandler = function(button) {
  return function() {
    if(!button.classList.contains("active"))
      button.classList.add("active");
    else
      button.classList.remove("active");
  };
};

for(var i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener("click", createToolBarButtonHandler(buttons[i]));
}

Note* The structure of the above code is a bit complicated. You can also simply use a closure to close and save the current button variable, as shown below:

var buttons = document.querySelectorAll(".toolbar .btn");

for(var i = 0; i < buttons.length; i++) {
  (function(button) {
    button.addEventListener("click", function() {
      if(!button.classList.contains("active"))
        button.classList.add("active");
      else
        button.classList.remove("active");
    });
  })(buttons[i])
}

Now it can work normally . Pointing to the correct button is always

So what’s the problem with this solution?

This solution looks okay, but we can indeed do better.

First of all, we created too many processing functions. An event listener and a callback handler are bound to each matching .toolbar button. If there are only three buttons this resource allocation can be ignored.

However, what if we have 1,000?

<ul class="toolbar">
  <li><button id="button_0001">Foo</button></li>
  <li><button id="button_0002">Bar</button></li>
  // ... 997 more elements ...
  <li><button id="button_1000">baz</button></li>
</ul>

It won’t crash, but it’s not the best solution. We allocate a lot of unnecessary functions. Let's refactor it to attach only once, binding only one function, to handle potentially thousands of calls.

Rather than closing the button variable to store the object we clicked at that time, we can use the event object to get the object we clicked at that time.

The event object has some metadata. In the case of multiple bindings, we can use currentTarget to obtain the currently bound object. The code in the above example can be changed to:

var buttons = document.querySelectorAll(".toolbar button");

var toolbarButtonHandler = function(e) {
  var button = e.currentTarget;
  if(!button.classList.contains("active"))
    button.classList.add("active");
  else
    button.classList.remove("active");
};

for(var i = 0; i < buttons.length; i++) {
  button.addEventListener("click", toolbarButtonHandler);
}

good! But this just simplifies a single function and makes it more readable, but it is still bound multiple times.

However, we can do better.

Let’s assume that we dynamically add some buttons to this list. Then we'll also add and remove event bindings for these dynamic elements. Then we have to persist the variables used by these processing functions and the current context, which sounds unreliable.

Maybe there are other ways.

Let’s first fully understand how events work and how they are delivered in the DOM.

How events work

When the user clicks on an element, an event is generated to notify the user of the current behavior. There are three stages when events are dispatched:

  • Capturing stage: Capturing

  • Triggering stage: Target

  • Bubbling phase: Bubbling

This event starts from before the document and then goes all the way down to find the object clicked by the current event. When the event reaches the clicked object, it will return along the original path (bubbling process) until it exits the entire DOM tree.

Here is an HTML example:

<html>
<body>
  <ul>
    <li id="li_1"><button id="button_1">Button A</button></li>
    <li id="li_2"><button id="button_2">Button B</button></li>
    <li id="li_3"><button id="button_3">Button C</button></li>
  </ul>
</body>
</html>

When you click Button A, the path of the event will be as follows:

START
| #document  \
| HTML        |
| BODY         } CAPTURE PHASE
| UL          |
| LI#li_1    /
| BUTTON     <-- TARGET PHASE
| LI#li_1    \
| UL          |
| BODY         } BUBBLING PHASE 
| HTML        |
v #document  /
END

Note that this means that you can capture the event generated by your click on the path of the event. We are very sure that this event will pass through their parent element ul element. We can bind our event processing to the parent element and simplify our solution. This is called event delegation and proxy (Delegated Events).

Note* In fact, the event mechanisms developed by Flash/Silverlight/WPF are very similar. Here is their event flow chart. Except that Silverlight 3 uses the old version of IE's event model with only the bubbling stage, it basically has these three stages. (The event processing of the old version of IE and SL3 only has a process of bubbling from the trigger object to the root object, which may be to simplify the event processing mechanism.)

  事件委托代理

  委托(代理)事件是那些被绑定到父级元素的事件,但是只有当满足一定匹配条件时才会被挪。

  让我们看一个具体的例子,我们看看上文的那个工具栏的例子:

<ul class="toolbar">
  <li><button class="btn">Pencil</button></li>
  <li><button class="btn">Pen</button></li>
  <li><button class="btn">Eraser</button></li>
</ul>

  因为我们知道单击button元素会冒泡到UL.toolbar元素,让我们将事件处理放到这里试试。我们需要稍微调整一下:

var toolbar = document.querySelector(".toolbar");
toolbar.addEventListener("click", function(e) {
  var button = e.target;
  if(!button.classList.contains("active"))
    button.classList.add("active");
  else
    button.classList.remove("active");
});

  这样我们清理了大量的代码,再也没有循环了。注意我们使用了e.target代替了之前的e.currentTarget。这是因为我们在一个不同的层次上面进行了事件侦听。

  • e.target 是当前触发事件的对象,即用户真正单击到的对象。

  • e.currentTarget 是当前处理事件的对象,即事件绑定的对象。

  在我们的例子中e.currentTarget就是UL.toolbar。

  注* 其实不止事件机制,在整个UI构架上FLEX(不是Flash) /Silverlight /WPF /Android的实现跟WEB也非常相似,都使用XML(HTML)实现模板及元素结构组织,Style(CSS)实现显示样式及UI,脚本(AS3,C#,Java,JS)实现控制。不过Web相对其他平台更加开放,不过历史遗留问题也更多。但是几乎所有的平台都支持Web标准,都内嵌有类似WebView这样的内嵌Web渲染机制,相对各大平台复杂的前端UI框架和学习曲线来说,使用Web技术实现Native APP的前端UI是非常低成本的一项选择。

  

 以上就是理解JavaScript中的事件路由冒泡过程及委托代理机制的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Previous article:firstchild in javascriptNext article:firstchild in javascript