摘要:我們可以利用這個現(xiàn)象和已知元素的層級簡化代碼,實現(xiàn)。注意這次用的是而非。如果一路檢查所有祖先元素,都不符合條件則不觸發(fā)處理函數(shù)。封裝上面已經(jīng)實現(xiàn)了在不使用的情況下實現(xiàn)。
想要實現(xiàn)類似于 jQuery 中類似于 .on() 中的 Delegated Event,卻又不想用 jQuery 怎么破?
先看問題舉個例子說明一下,有一組按鈕,每當(dāng)點擊其中一個按鈕,就把這個按鈕的狀態(tài)變?yōu)?"active",再點一下就取消 "active" 狀態(tài),代碼如下:
用最普通的 js 可以這樣處理:
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"); }); }
不過并沒有達到預(yù)期的效果。
閉包惹的禍有經(jīng)驗的讀者可能已經(jīng)看出不對勁的地方了。那是因為處理點擊事件的 handler 函數(shù)形成獨立的作用域,是其中的 button 會嘗試去更上級的作用域去尋找。
不過真正當(dāng)你去點擊按鈕的時候,循環(huán)已經(jīng)完成,button 就會一直指向最后一個按鈕,所以效果就是不管點擊哪個按鈕都是最后一個按鈕的狀態(tài)在變化。
把代碼改善一下:
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++) { button.addEventListener("click", createToolBarButtonHandler(buttons[i])); }
好了,現(xiàn)在就滿足要求了。
不過。。。雖然可以勉強使用,但還可以做地更好一些。
首先上面的代碼會產(chǎn)生許多 handler,在只有三個按鈕的時候還是可以接受的。
不過當(dāng)有上千個按鈕需要監(jiān)聽點擊事件的情況:
就沒那么輕松了,雖說不會崩潰,但這種方式非常不理想。上面的實現(xiàn)方式是綁定了好多不同的卻功能相似的函數(shù),其實根本不需要這樣。只需要綁定一個共享的函數(shù)就夠了。
改動很簡單,可以使用對應(yīng)的事件對象作為 handler 的參數(shù),就可以通過event.currentTarget很方便地找到對應(yīng)點擊的按鈕了。
譯者注:這里的 event.currentTarget 也就相當(dāng)于 handler 中的 this。
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); }
到此我們的確實現(xiàn)了綁定同一個 handler,而且增加了代碼的可讀性。
不過還可以做的更好。
假設(shè)這樣一種場景,按鈕組中會動態(tài)的添加新的按鈕進來,這樣就還得在新添加的按鈕上綁定監(jiān)聽處理。這就有點麻煩了。
不如換一種方法。
先回想一下 DOM 中 event 的工作原理。
DOM Event 的工作原理簡析當(dāng)點擊一個元素,會產(chǎn)生一個點擊事件,這個事件分為三個階段。
Capturing 捕獲階段
Target 目標(biāo)階段
Bubbling 冒泡階段
NOTE: Not all events bubble/capture, instead they are dispatched directly on the target, but most do.
The event starts outside the document and then descends through the DOM hierarchy to the target of the event. Once the event reaches it"s target, it then turns around and heads back out the same way, until it exits the DOM.
注:雖然并不是所有事件的都有 冒泡/捕獲 階段,但絕大部分都有。捕獲階段是從最外層的 document 開始,穿過目標(biāo)元素的祖先元素,到達目標(biāo)元素,然后再原路冒泡回到 document。
從一段 HTML 代碼的例子來看:
如果點擊 Button A 按鈕,事件的過程是這樣的:
START | #document | HTML | | BODY } CAPTURE PHASE | UL | | LI#li_1 / | BUTTON <-- TARGET PHASE | LI#li_1 | UL | | BODY } BUBBLING PHASE | HTML | v #document / END
我們可以注意到在事件的冒泡階段,按鈕的祖先元素 ul 也可以收到點擊事件。我們可以利用這個現(xiàn)象和已知元素的層級簡化代碼,實現(xiàn) Delegated Events。
Delegated EventsDelegated Events 是把事件處理綁定在真正需要被綁定元素的祖先元素上,然后通過一定的條件篩選出真正需要被綁定的元素。
還是最初的代碼:
既然每次事件冒泡的階段 ul.toolbar 也可以收到點擊事件,我們就把事件綁定在它上面。修改對應(yīng)的 js 代碼:
var toolbar = document.querySelectorAll(".toolbar"); toolbar.addEventListener("click", function(e) { var button = e.target; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); });
That cleaned up a lot of code, and we have no more loops! Notice that we use e.target instead of e.currentTarget as we did before. That is because we are listening for the event at a different level.
去掉了 for 循環(huán)使代碼看起來清爽多了。注意這次用的是 e.target 而非 e.currentTarget。
e.target 是事件的目標(biāo)元素,也就是例子的 button.btn
e.currentTarget 是被綁定事件處理的元素,也就是例子中的 ul.toolbar
More Robust Delegated Events現(xiàn)在已經(jīng)可以處理所有 ul.toolbar 后代元素的點擊事件,不過這樣有些太簡單了,我們需要過濾掉不能被點擊的后代元素:
我們并不需要處理對 li.separator 的點擊事件,那就加一個過濾輔助函數(shù):
var delegate = function(criteria, listener) { return function(e) { var el = e.target; do { if (!criteria(el)) continue; e.delegateTarget = el; listener.apply(this, arguments); return; } while( (el = el.parentNode) ); }; };
這個過濾輔助函數(shù)的作用,一是判斷 e.target 和它的所有祖先元素是否滿足過濾條件。如果滿足就在事件對象上增加一個 delegateTarget 屬性,用于后面使用,然后調(diào)用事件的處理函數(shù)。如果一路檢查所有祖先元素,都不符合條件則不觸發(fā)處理函數(shù)。
具體使用:
var toolbar = document.querySelector(".toolbar"); var buttonsFilter = function(elem) { return elem.classList && elem.classList.contains("btn"); }; var buttonHandler = function(e) { var button = e.delegateTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; toolbar.addEventListener("click", delegate(buttonsFilter, buttonHandler));
沒錯!就是這個意思。只需要在一個元素上綁定一個 handler,就夠了。并且也不需要擔(dān)心動態(tài)增加的元素。這就是所謂的 Delegated Events。
封裝上面已經(jīng)實現(xiàn)了在不使用 jQuery 的情況下實現(xiàn) Delegated Events。
還可以把代碼進一步封裝一下:
Create helper functions to handle criteria matching in a unified functional way. Something like:
var criteria = { isElement: function(e) { return e instanceof HTMLElement; }, hasClass: function(cls) { return function(e) { return criteria.isElement(e) && e.classList.contains(cls); } } // More criteria matchers };
A partial application helper would also be nice:
var partialDelgate = function(criteria) { return function(handler) { return delgate(criteria, handler); } };
原文鏈接
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/85367.html
摘要:接受個參數(shù)事件類型,是否冒泡,是否阻止瀏覽器的默認(rèn)行為觸發(fā)上綁定的自定義事件觸發(fā)元素上綁定事件事件的委托代理的原理以及優(yōu)缺點。委托代理事件是那些被綁定到父級元素的事件,但是只有當(dāng)滿足一定匹配條件時才會被挪。 一、頁面布局 1.問題:假設(shè)高度已知,請寫出三欄布局 ,其中左欄、右欄寬度各為300px,中間自適應(yīng)。 解決方案一:使用浮動布局` Document ...
摘要:接受個參數(shù)事件類型,是否冒泡,是否阻止瀏覽器的默認(rèn)行為觸發(fā)上綁定的自定義事件觸發(fā)元素上綁定事件事件的委托代理的原理以及優(yōu)缺點。委托代理事件是那些被綁定到父級元素的事件,但是只有當(dāng)滿足一定匹配條件時才會被挪。 一、頁面布局 1.問題:假設(shè)高度已知,請寫出三欄布局 ,其中左欄、右欄寬度各為300px,中間自適應(yīng)。 解決方案一:使用浮動布局` Document ...
摘要:接受個參數(shù)事件類型,是否冒泡,是否阻止瀏覽器的默認(rèn)行為觸發(fā)上綁定的自定義事件觸發(fā)元素上綁定事件事件的委托代理的原理以及優(yōu)缺點。委托代理事件是那些被綁定到父級元素的事件,但是只有當(dāng)滿足一定匹配條件時才會被挪。 一、頁面布局 1.問題:假設(shè)高度已知,請寫出三欄布局 ,其中左欄、右欄寬度各為300px,中間自適應(yīng)。 解決方案一:使用浮動布局` Document ...
摘要:使用構(gòu)造函數(shù)那么有沒有一種辦法,可以不寫函數(shù)名,直接聲明一個函數(shù)并自動調(diào)用它呢答案肯定的,那就是使用自執(zhí)行函數(shù)。 日常工作中經(jīng)常會發(fā)現(xiàn)有大量業(yè)務(wù)邏輯是重復(fù)的,而用別人的插件也不能完美解決一些定制化的需求,所以我決定把一些常用的組件抽離、封裝出來,形成一套自己的插件庫。同時,我將用這個教程系列記錄下每一個插件的開發(fā)過程,手把手教你如何一步一步去造出一套實用性、可復(fù)用性高的輪子。 So, ...
閱讀 3347·2021-11-22 15:22
閱讀 2871·2021-10-12 10:12
閱讀 2167·2021-08-21 14:10
閱讀 3833·2021-08-19 11:13
閱讀 2852·2019-08-30 15:43
閱讀 3233·2019-08-29 16:52
閱讀 451·2019-08-29 16:41
閱讀 1439·2019-08-29 12:53