摘要:我們可以利用這個(gè)現(xiàn)象和已知元素的層級(jí)簡(jiǎn)化代碼,實(shí)現(xiàn)。注意這次用的是而非。如果一路檢查所有祖先元素,都不符合條件則不觸發(fā)處理函數(shù)。封裝上面已經(jīng)實(shí)現(xiàn)了在不使用的情況下實(shí)現(xiàn)。
想要實(shí)現(xiàn)類似于 jQuery 中類似于 .on() 中的 Delegated Event,卻又不想用 jQuery 怎么破?
先看問(wèn)題舉個(gè)例子說(shuō)明一下,有一組按鈕,每當(dāng)點(diǎn)擊其中一個(gè)按鈕,就把這個(gè)按鈕的狀態(tài)變?yōu)?"active",再點(diǎn)一下就取消 "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"); }); }
不過(guò)并沒(méi)有達(dá)到預(yù)期的效果。
閉包惹的禍有經(jīng)驗(yàn)的讀者可能已經(jīng)看出不對(duì)勁的地方了。那是因?yàn)樘幚睃c(diǎn)擊事件的 handler 函數(shù)形成獨(dú)立的作用域,是其中的 button 會(huì)嘗試去更上級(jí)的作用域去尋找。
不過(guò)真正當(dāng)你去點(diǎn)擊按鈕的時(shí)候,循環(huán)已經(jīng)完成,button 就會(huì)一直指向最后一個(gè)按鈕,所以效果就是不管點(diǎn)擊哪個(gè)按鈕都是最后一個(gè)按鈕的狀態(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)在就滿足要求了。
不過(guò)。。。雖然可以勉強(qiáng)使用,但還可以做地更好一些。
首先上面的代碼會(huì)產(chǎn)生許多 handler,在只有三個(gè)按鈕的時(shí)候還是可以接受的。
不過(guò)當(dāng)有上千個(gè)按鈕需要監(jiān)聽(tīng)點(diǎn)擊事件的情況:
就沒(méi)那么輕松了,雖說(shuō)不會(huì)崩潰,但這種方式非常不理想。上面的實(shí)現(xiàn)方式是綁定了好多不同的卻功能相似的函數(shù),其實(shí)根本不需要這樣。只需要綁定一個(gè)共享的函數(shù)就夠了。
改動(dòng)很簡(jiǎn)單,可以使用對(duì)應(yīng)的事件對(duì)象作為 handler 的參數(shù),就可以通過(guò)event.currentTarget很方便地找到對(duì)應(yīng)點(diǎn)擊的按鈕了。
譯者注:這里的 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); }
到此我們的確實(shí)現(xiàn)了綁定同一個(gè) handler,而且增加了代碼的可讀性。
不過(guò)還可以做的更好。
假設(shè)這樣一種場(chǎng)景,按鈕組中會(huì)動(dòng)態(tài)的添加新的按鈕進(jìn)來(lái),這樣就還得在新添加的按鈕上綁定監(jiān)聽(tīng)處理。這就有點(diǎn)麻煩了。
不如換一種方法。
先回想一下 DOM 中 event 的工作原理。
DOM Event 的工作原理簡(jiǎn)析當(dāng)點(diǎn)擊一個(gè)元素,會(huì)產(chǎn)生一個(gè)點(diǎn)擊事件,這個(gè)事件分為三個(gè)階段。
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 開(kāi)始,穿過(guò)目標(biāo)元素的祖先元素,到達(dá)目標(biāo)元素,然后再原路冒泡回到 document。
從一段 HTML 代碼的例子來(lái)看:
如果點(diǎn)擊 Button A 按鈕,事件的過(guò)程是這樣的:
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 也可以收到點(diǎn)擊事件。我們可以利用這個(gè)現(xiàn)象和已知元素的層級(jí)簡(jiǎn)化代碼,實(shí)現(xiàn) Delegated Events。
Delegated EventsDelegated Events 是把事件處理綁定在真正需要被綁定元素的祖先元素上,然后通過(guò)一定的條件篩選出真正需要被綁定的元素。
還是最初的代碼:
既然每次事件冒泡的階段 ul.toolbar 也可以收到點(diǎn)擊事件,我們就把事件綁定在它上面。修改對(duì)應(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)使代碼看起來(lái)清爽多了。注意這次用的是 e.target 而非 e.currentTarget。
e.target 是事件的目標(biāo)元素,也就是例子的 button.btn
e.currentTarget 是被綁定事件處理的元素,也就是例子中的 ul.toolbar
More Robust Delegated Events現(xiàn)在已經(jīng)可以處理所有 ul.toolbar 后代元素的點(diǎn)擊事件,不過(guò)這樣有些太簡(jiǎn)單了,我們需要過(guò)濾掉不能被點(diǎn)擊的后代元素:
我們并不需要處理對(duì) li.separator 的點(diǎn)擊事件,那就加一個(gè)過(guò)濾輔助函數(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) ); }; };
這個(gè)過(guò)濾輔助函數(shù)的作用,一是判斷 e.target 和它的所有祖先元素是否滿足過(guò)濾條件。如果滿足就在事件對(duì)象上增加一個(gè) 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));
沒(méi)錯(cuò)!就是這個(gè)意思。只需要在一個(gè)元素上綁定一個(gè) handler,就夠了。并且也不需要擔(dān)心動(dòng)態(tài)增加的元素。這就是所謂的 Delegated Events。
封裝上面已經(jīng)實(shí)現(xiàn)了在不使用 jQuery 的情況下實(shí)現(xiàn) Delegated Events。
還可以把代碼進(jìn)一步封裝一下:
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)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/85367.html
摘要:接受個(gè)參數(shù)事件類型,是否冒泡,是否阻止瀏覽器的默認(rèn)行為觸發(fā)上綁定的自定義事件觸發(fā)元素上綁定事件事件的委托代理的原理以及優(yōu)缺點(diǎn)。委托代理事件是那些被綁定到父級(jí)元素的事件,但是只有當(dāng)滿足一定匹配條件時(shí)才會(huì)被挪。 一、頁(yè)面布局 1.問(wèn)題:假設(shè)高度已知,請(qǐng)寫出三欄布局 ,其中左欄、右欄寬度各為300px,中間自適應(yīng)。 解決方案一:使用浮動(dòng)布局` Document ...
摘要:接受個(gè)參數(shù)事件類型,是否冒泡,是否阻止瀏覽器的默認(rèn)行為觸發(fā)上綁定的自定義事件觸發(fā)元素上綁定事件事件的委托代理的原理以及優(yōu)缺點(diǎn)。委托代理事件是那些被綁定到父級(jí)元素的事件,但是只有當(dāng)滿足一定匹配條件時(shí)才會(huì)被挪。 一、頁(yè)面布局 1.問(wèn)題:假設(shè)高度已知,請(qǐng)寫出三欄布局 ,其中左欄、右欄寬度各為300px,中間自適應(yīng)。 解決方案一:使用浮動(dòng)布局` Document ...
摘要:接受個(gè)參數(shù)事件類型,是否冒泡,是否阻止瀏覽器的默認(rèn)行為觸發(fā)上綁定的自定義事件觸發(fā)元素上綁定事件事件的委托代理的原理以及優(yōu)缺點(diǎn)。委托代理事件是那些被綁定到父級(jí)元素的事件,但是只有當(dāng)滿足一定匹配條件時(shí)才會(huì)被挪。 一、頁(yè)面布局 1.問(wèn)題:假設(shè)高度已知,請(qǐng)寫出三欄布局 ,其中左欄、右欄寬度各為300px,中間自適應(yīng)。 解決方案一:使用浮動(dòng)布局` Document ...
摘要:使用構(gòu)造函數(shù)那么有沒(méi)有一種辦法,可以不寫函數(shù)名,直接聲明一個(gè)函數(shù)并自動(dòng)調(diào)用它呢答案肯定的,那就是使用自執(zhí)行函數(shù)。 日常工作中經(jīng)常會(huì)發(fā)現(xiàn)有大量業(yè)務(wù)邏輯是重復(fù)的,而用別人的插件也不能完美解決一些定制化的需求,所以我決定把一些常用的組件抽離、封裝出來(lái),形成一套自己的插件庫(kù)。同時(shí),我將用這個(gè)教程系列記錄下每一個(gè)插件的開(kāi)發(fā)過(guò)程,手把手教你如何一步一步去造出一套實(shí)用性、可復(fù)用性高的輪子。 So, ...
閱讀 3338·2021-11-22 15:22
閱讀 2862·2021-10-12 10:12
閱讀 2156·2021-08-21 14:10
閱讀 3822·2021-08-19 11:13
閱讀 2841·2019-08-30 15:43
閱讀 3223·2019-08-29 16:52
閱讀 438·2019-08-29 16:41
閱讀 1427·2019-08-29 12:53