W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
捕獲和冒泡允許我們實現(xiàn)最強大的事件處理模式之一,即 事件委托 模式。
這個想法是,如果我們有許多以類似方式處理的元素,那么就不必為每個元素分配一個處理程序 —— 而是將單個處理程序放在它們的共同祖先上。
在處理程序中,我們獲取 event.target
以查看事件實際發(fā)生的位置并進行處理。
讓我們看一個示例 —— 反映中國古代哲學(xué)的 八卦圖。
如下所示:
其 HTML 如下所示:
<table>
<tr>
<th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
</tr>
<tr>
<td class="nw"><strong>Northwest</strong><br>Metal<br>Silver<br>Elders</td>
<td class="n">...</td>
<td class="ne">...</td>
</tr>
<tr>...2 more lines of this kind...</tr>
<tr>...2 more lines of this kind...</tr>
</table>
該表格有 9 個單元格(cell),但可以有 99 個或 9999 個單元格,這都不重要。
我們的任務(wù)是在點擊時高亮顯示被點擊的單元格 <td>
。
與其為每個 <td>
(可能有很多)分配一個 onclick
處理程序 —— 我們可以在 <table>
元素上設(shè)置一個“捕獲所有”的處理程序。
它將使用 event.target
來獲取點擊的元素并高亮顯示它。
代碼如下:
let selectedTd;
table.onclick = function(event) {
let target = event.target; // 在哪里點擊的?
if (target.tagName != 'TD') return; // 不在 TD 上?那么我們就不會在意
highlight(target); // 高亮顯示它
};
function highlight(td) {
if (selectedTd) { // 移除現(xiàn)有的高亮顯示,如果有的話
selectedTd.classList.remove('highlight');
}
selectedTd = td;
selectedTd.classList.add('highlight'); // 高亮顯示新的 td
}
此代碼不會關(guān)心在表格中有多少個單元格。我們可以隨時動態(tài)添加/移除 <td>
,高亮顯示仍然有效。
盡管如此,但還是存在缺陷。
點擊可能不是發(fā)生在 <td>
上,而是發(fā)生在其內(nèi)部。
在我們的例子中,如果我們看一下 HTML 內(nèi)部,我們可以看到 <td>
內(nèi)還有嵌套的標簽,例如 <strong>
:
<td>
<strong>Northwest</strong>
...
</td>
自然地,如果在該 <strong>
上點擊,那么它將成為 event.target
的值。
在處理程序 table.onclick
中,我們應(yīng)該接受這樣的 event.target
,并確定該點擊是否在 <td>
內(nèi)。
下面是改進后的代碼:
table.onclick = function(event) {
let td = event.target.closest('td'); // (1)
if (!td) return; // (2)
if (!table.contains(td)) return; // (3)
highlight(td); // (4)
};
解釋:
elem.closest(selector)
? 方法返回與 ?selector
? 匹配的最近的祖先。在我們的例子中,我們從源元素開始向上尋找 ?<td>
?。event.target
? 不在任何 ?<td>
? 中,那么調(diào)用將立即返回,因為這里沒有什么事兒可做。event.target
? 可能是一個 ?<td>
?,但位于當前表格之外。因此我們需要檢查它是否是 我們的表格中的 ?<td>
?。最終,我們得到了一個快速、高效的用于高亮顯示的代碼,該代碼與表格中的 <td>
的數(shù)量無關(guān)。
事件委托還有其他用途。(譯注:本節(jié)標題中的“標記中的行為”即 action in markup)
例如,我們想要編寫一個有“保存”、“加載”和“搜索”等按鈕的菜單。并且,這里有一個具有 save
、load
和 search
等方法的對象。如何匹配它們?
第一個想法可能是為每個按鈕分配一個單獨的處理程序。但是有一個更優(yōu)雅的解決方案。我們可以為整個菜單添加一個處理程序,并為具有方法調(diào)用的按鈕添加 data-action
特性(attribute):
<button data-action="save">Click to Save</button>
處理程序讀取特性(attribute)并執(zhí)行該方法。工作示例如下:
<div id="menu">
<button data-action="save">Save</button>
<button data-action="load">Load</button>
<button data-action="search">Search</button>
</div>
<script>
class Menu {
constructor(elem) {
this._elem = elem;
elem.onclick = this.onClick.bind(this); // (*)
}
save() {
alert('saving');
}
load() {
alert('loading');
}
search() {
alert('searching');
}
onClick(event) {
let action = event.target.dataset.action;
if (action) {
this[action]();
}
};
}
new Menu(menu);
</script>
請注意,this.onClick
在 (*)
行中被綁定到了 this
。這很重要,因為否則內(nèi)部的 this
將引用 DOM 元素(elem
),而不是 Menu
對象,那樣的話,this[action]
將不是我們所需要的。
那么,這里的委托給我們帶來了什么好處?
- 我們不需要編寫代碼來為每個按鈕分配一個處理程序。只需要創(chuàng)建一個方法并將其放入標記(markup)中即可。
- HTML 結(jié)構(gòu)非常靈活,我們可以隨時添加/移除按鈕。
我們也可以使用 .action-save
,.action-load
類,但 data-action
特性(attribute)在語義上更好。我們也可以在 CSS 規(guī)則中使用它。
我們還可以使用事件委托將“行為(behavior)”以 聲明方式 添加到具有特殊特性(attribute)和類的元素中。
行為模式分為兩個部分:
例如,這里的特性 data-counter
給按鈕添加了一個“點擊增加”的行為。
Counter: <input type="button" value="1" data-counter>
One more counter: <input type="button" value="2" data-counter>
<script>
document.addEventListener('click', function(event) {
if (event.target.dataset.counter != undefined) { // 如果這個特性存在...
event.target.value++;
}
});
</script>
如果我們點擊按鈕 —— 它的值就會增加。但不僅僅是按鈕,一般的方法在這里也很重要。
我們可以根據(jù)需要使用 data-counter
特性,多少都可以。我們可以隨時向 HTML 添加新的特性。使用事件委托,我們屬于對 HTML 進行了“擴展”,添加了描述新行為的特性。
對于文檔級的處理程序 —— 始終使用的是 ?
addEventListener
?當我們將事件處理程序分配給
document
對象時,我們應(yīng)該始終使用addEventListener
,而不是document.on<event>
,因為后者會引起沖突:新的處理程序會覆蓋舊的處理程序。
對于實際項目來說。在
document
上有許多由代碼的不同部分設(shè)置的處理程序,這是很正常的。
再舉一個例子。點擊一個具有 data-toggle-id
特性的元素將顯示/隱藏具有給定 id
的元素:
<button data-toggle-id="subscribe-mail">
Show the subscription form
</button>
<form id="subscribe-mail" hidden>
Your mail: <input type="email">
</form>
<script>
document.addEventListener('click', function(event) {
let id = event.target.dataset.toggleId;
if (!id) return;
let elem = document.getElementById(id);
elem.hidden = !elem.hidden;
});
</script>
讓我們再次注意我們做了什么。現(xiàn)在,要向元素添加切換功能 —— 無需了解 JavaScript,只需要使用特性 data-toggle-id
即可。
這可能變得非常方便 —— 無需為每個這樣的元素編寫 JavaScript。只需要使用行為。文檔級處理程序使其適用于頁面的任意元素。
我們也可以組合單個元素上的多個行為。
“行為”模式可以替代 JavaScript 的小片段。
事件委托真的很酷!這是 DOM 事件最有用的模式之一。
它通常用于為許多相似的元素添加相同的處理,但不僅限于此。
算法:
event.target
?。好處:
- 簡化初始化并節(jié)省內(nèi)存:無需添加許多處理程序。
- 更少的代碼:添加或移除元素時,無需添加/移除處理程序。
- DOM 修改 :我們可以使用 ?
innerHTML
? 等,來批量添加/移除元素。
事件委托也有其局限性:
- 首先,事件必須冒泡。而有些事件不會冒泡。此外,低級別的處理程序不應(yīng)該使用 ?
event.stopPropagation()
?。- 其次,委托可能會增加 CPU 負載,因為容器級別的處理程序會對容器中任意位置的事件做出反應(yīng),而不管我們是否對該事件感興趣。但是,通常負載可以忽略不計,所以我們不考慮它。
有一個帶有移除按鈕 ?[x]
? 的消息列表。讓按鈕可以工作。
P.S. 在容器上應(yīng)該只有一個事件監(jiān)聽器,請使用事件委托。
創(chuàng)建一個點擊可以顯示/隱藏子節(jié)點的樹形菜單:
要求:
解決方案分為兩個部分。
<span>
? 中。然后我們可以在 ?:hover
? 上使用 CSS 樣式,并精確地處理文本上的點擊事件,因為 ?<span>
? 的寬度恰好是文本的寬度(與沒有寬度不同)。 tree
? 的根節(jié)點設(shè)置一個處理程序,來處理 ?<span>
? 標題上的點擊事件。使表格可排序:點擊 <th>
元素,應(yīng)按對應(yīng)的列對表格進行排序。
每個 <th>
的特性(attribute)中都有類型,如下所示:
<table id="grid">
<thead>
<tr>
<th data-type="number">Age</th>
<th data-type="string">Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>5</td>
<td>John</td>
</tr>
<tr>
<td>10</td>
<td>Ann</td>
</tr>
...
</tbody>
</table>
在上面的示例中,第一列為數(shù)字,第二列為字符串。排序函數(shù)應(yīng)根據(jù)類型進行排序。
應(yīng)該只支持 "string"
和 "number"
類型。
P.S. 表格可以更大,有任意數(shù)量的行和列。
編寫工具提示(tooltip)行為的 JavaScript 代碼。
當鼠標在帶有 data-tooltip
的元素的上方時,工具提示應(yīng)顯示在其上方,當鼠標移開時,工具提示將隱藏起來。
在此任務(wù)中,我們假設(shè)所有具有 data-tooltip
的元素中都只有文本。尚無嵌套標簽。
詳情:
5px
?。data-tooltip
? 屬性中給定。它可以是任意 HTML。在這里你將需要兩個事件:
mouseover
? 當鼠標指針出現(xiàn)在元素上方時觸發(fā)。mouseout
? 當鼠標指針離開元素時觸發(fā)。請使用事件委托:在 document
上設(shè)置兩個處理程序,以跟蹤帶有 data-tooltip
的元素中的所有 “over” 和 “out”,并從那里管理工具提示。
在實現(xiàn)了該行為后,即使不熟悉 JavaScript 的人也可以添加帶注釋的元素。
P.S. 一次只能顯示一個工具提示。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: