首頁  >  文章  >  web前端  >  javaScript中的事件路由冒泡過程及委託代理機制

javaScript中的事件路由冒泡過程及委託代理機制

伊谢尔伦
伊谢尔伦原創
2016-11-23 10:11:45911瀏覽

當我用純CSS實現這個以後。我開始用JavaScript和樣式類別來完善功能。

  然後,我有一些想法,我想使用Delegated Events (事件委託)但是我不想有任何依賴,插入任何庫,包括jQuery。我需要自己實現事件委託了。

  我們先來看看事件委託到底是什麼?他們是怎麼工作的,怎麼去實現這個機制。

  好,它解決了什麼問題?

  我們先來看個簡單的例子。

  先假設我們有一組按鈕,我一次點擊一個按鈕,然後我希望被點中的狀態設為"active"。再次點擊時取消active。

  然後,我們可以寫一些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>

  我可以用一些標準的Javascript事件處理上面的邏輯: 

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");
  });
}

  看上去不錯,但是它其實不能像你期望的那樣工作。 

  閉包的陷阱

  如果你有一定的JavaScript開發經驗,這個問題就很明顯了。

  對於外行人來說button變數是被封閉的,每次都會找到對應的button……但是其實這裡只有一個button;每次循環都會被重新分配。

  第一個循環它指向第一個button,接下來是第二個。但當你點擊時button變數永遠只指向最後一個button元素,問題出在這裡。

  我們需要的是一個穩定的作用域;讓我們重構一下。

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]));
}

  註* 上面這段程式碼結構有點複雜,也可以簡單直接地使用一個閉包,封閉保存當前的button變量,如下所示:

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])
}

  現在它能正常工作了。指向永遠是正確的button 

  那麼這個方案有什麼問題?

  這個方案看起來還可以,然而我們確實可以做得更好。

  首先我們創建了太多的處理函數。為每一個匹配的.toolbar button綁定了一個事件偵聽和一個回調處理。假如只有三個按鈕這種資源分配是可以忽略的。

  然而,如果我們有1000個呢?

<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>

  它也不會崩潰,但這並不是最佳的方案。我們分配了大量不必要的函數。讓我們重構一下,僅附加一次,即僅綁定一個函數(function),去處理這種有可能的數千次呼叫。

  相對於封閉button變數去儲存當時我們點擊的對象,我們可以使用event對象去取得當時點擊的對象。

  event對像有一些元數據,在多次綁定的種情況下,我們可以使用currentTarget獲取當前綁定的對象,如上例的代碼就可以改成:

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);
}

  不錯!不過這只是簡化了單一函數,讓它得更具可讀性,然而它還是被綁定了多次。

  但是,我們還可以做得更好。

  讓我們假設一下,我們在這個清單裡動態地加入了一些按鈕。然後我們也要為這些動態元素新增和移除事件綁定。然後我們還要持久化這些處理函數和當前上下文要用到的變量,這事聽起來就不靠譜。

  也許還有其他方法。

  讓我們先全面理解一下事件的工作原理,以及他們在DOM裡是怎樣傳遞的。

  事件的工作原理

  當使用者點擊一個元素時,一個事件就會被產生去通知使用者當前的行為。事件在分送派遣時會有三個階段:

捕獲階段: Capturing 

觸發階段: Target

冒泡階段: Bubbling

  這個事件起始從document之前然後一路向下找到當前事件點擊到的對象。當事件達到點擊到的物件之後,它會按原路返回(冒泡過程),直到退出整個DOM樹。

  這裡有一個HTML的例子:

<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>

當你點選Button A時,事件經過的路徑會向下面這樣:

START
| # | UL          |
| LI#li_1    /
| BUTTON     | LI#li_1    
| UL      .     |
v #document  /
END

  注意,意思是你可以在事件的經過路徑上捕獲到你點擊所產生的事件,我們非常確定這個事件一定會經過他們的父元素ul元素。我們可以將我們的事件處理綁定到父元素上面,然後簡化我們的解決方案,這個就叫事件的委託及代理(Delegated Events)。

  注* 其实Flash/Silverlight/WPF开发的事件机制是非常近似的,这里有一张他们的事件流程图。 除了Silverlight 3使用了旧版IE的仅有冒泡阶段的事件模型外,基本上也都有这三个阶段。(旧版IE和SL3的事件处理只有一个从触发对象冒泡到根对象的过程,可能是为了简化事件的处理机制。)

javaScript中的事件路由冒泡過程及委託代理機制

事件委托代理

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

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

<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是非常低成本的一项选择。


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn