如果你經常使用jQuery,那麼你也許很熟悉事件繫結。這是很基本的東西,但是深入一點,你就能夠找到機會讓你事件驅動的程式碼變得不太零碎,並且更容易管理。
更好的選擇器策略
讓我們從基礎的例子開始。下面的HTML程式碼表示的是可以開合的導航選單。
<button class="nav-menu-toggle">Toggle Nav Menu</button>
<nav>
<ul>
<li><a href="/">West Philadelphia</a></li>
<li><a href="/cab">Cab Whistling</a></li>
<li><a href="/throne">Throne Sitting</a></li>
</ul>
</nav>
下面這個是點選按鈕之後控制導航選單開合的javascript程式碼
$(`.nav-menu-toggle`).on(`click`,function(){
$(`nav`).toggle();
});
這可能是最常用的實現方式。它能夠使用,但是比較脆。javascript程式碼依賴了按鈕的類名nav-menu-toggle
。很可能在未來其他開發者或者健忘的你在重構程式碼的時候會刪除或者重新命名這個類名。
問題的核心是我們同時在表現和互動中使用了CSS的類名。這違反了關注點分離的原則,讓維護更容易出錯。
讓我們用一個不同的方法來實現
<button data-hook="nav-menu-toggle">Toggle Nav Menu</button>
<nav data-hook="nav-menu">
<ul>
<li><a href="/">West Philadelphia</a></li>
<li><a href="/cab">Cab Whistling</a></li>
<li><a href="/throne">Throne Sitting</a></li>
</ul>
</nav>
這次我們使用這個data屬性(data-hook)來選擇元素。任何對CSS類的改變將不會影響到javascript,讓我們能夠實現關注點分離以及更加穩定的程式碼。
下面我們用data-hook
屬性來選擇對應的元素:
$(`[data-hook="nav-menu-toggle"]`).on(`click`,function(){
$(`[data-hook="nav-menu"]`).toggle();
});
需要注意的是,我也使用data-hook
作為nav
元素的選擇器。你不一定需要,但是我喜歡這裡麵包含的思想:任何使用你看到data-hook
,你會知道這個元素在javascript中引用到啦。
一些語法糖
我必須承認data-hook
選擇器並不是很漂亮。讓我們通過擴充套件jQuery實現一個自定義的函式:
$.extend({
hook:function(hookName){
var selector;
if(!hookName || hookName === `*`){
// select all data-hooks
selector=`[data-hook]`
}else{
// select specific data-hook
selector=`[data-hook*="`+hookName+`"]`;
}
return $(selector);
}
});
上面準備完畢,我們來重寫一下javascript。
$.hook(`nav-menu-toggle`).on(``,function(){
$.hook(`nav-menu`).toggle();
});
更好的是,我們甚至可以把一系列以空格分開的hook名字放在一個元素上。
<button data-hook="nav-menu-toggle video-pause click-track">Toggle Nav Menu</button>
我們可以找到裡面的任意個hook名字:
$.hook(`click-track`); // returns the button as expected
我們也能夠找到頁面上所有的hook元素
// both are equivalent
$.hook();
$.hook(`*`);
防止函式表示式
到目前為止,我們在事件處理中使用的都是匿名函式。讓我們重寫一下,使用宣告的函式來代替它。
function toggleNavMenu(){
$.hook(`nav-menu`).toggle();
}
$.hook(`nav-menu-toggle`).on(`click`,toggleNavMenu);
這讓事件繫結的程式碼更加易讀。這個toggleNavMenu
函式名錶達了意圖,是程式碼自我註釋的好例子。
我們同時也獲得了可複用的能力,因為其他地方可能需要使用toggleNavMenu
函式。
最後,這對於自動化測試來說是意見大喜事,因為宣告的函式的單元測試要比匿名函式單元測試容易的多。
同時使用多個事件
jQuery提供了一個簡單方便的語法來處理多事件的問題。比如,你可以為一系列空格隔開的事件列表繫結同一個事件處理函式。
$.hook(`nav-menu-toggle`).on(`click keydown mouseenter`,trackAction);
如果你需要為不同的事件繫結不同的處理函式,你可以使用物件表達方式:
$.hook(`nav-menu-toggle`).on({
`click`:trackClick,
`keydown`:tranckKeyDown,
`mouseenter`:trackMouseEnter
});
反過來,你可以同時取消多個事件的繫結:
// unbinds keydown and mouseenter
$.hook(`nav-menu-toggle`).off(`keydown mouseenter`);
// nuclear options:unbinds everything
$.hook(`nav-menu-toggle`).off();
你可以想象到的是,不小心的取消事件繫結可能會導致嚴重的我們不想要的副作用。繼續看我們可以通過哪些技巧來減輕這個問題。
小心的取消事件繫結
一般情況下我們不會在一個元素的同一事件型別繫結多個事件處理函式。讓我們再看一下之前的那個按鈕:
<button data-hook="nav-menu-toggle video-pause click-track">Toggle Nav Menu</button>
不同的程式碼區域可能會在同一個元素的同一事件繫結不同的事件處理函式:
// somewhere in the nav code
$.hook(`nav-menu-toggle`).on(`click`,toggleNavMenu);
// somewhere in the video playback code
$.hook(`video-pause`).on(`click`,pauseCarltonDanceVideo);
// somewhere in the analytics code
$.hook(`click-track`).on(`click`,trackClick);
儘管我們使用了不同的選擇器,但是這個元素現在有三個事件處理函式啦。假如我們的分析程式碼不在關心這個按鈕:
// no good
$.hook(`click-track`).off(`click`);
糟糕的是,上面的程式碼實際上回刪除所有的點選事件處理函式,不僅僅是trackClick
。我們應該實用更加有辨別力的方式來指定我們需要刪除的事件處理函式:
$.hook(`click-track`).off(`click`,trackClick);
另一種方式是使用名稱空間。任何事件都有資格使用一個名稱空間來實現繫結和取消繫結,這樣你就可以更好的控制事件繫結和取消繫結。
// binds a click event in the "analytics" namespace
$.hook(`click-track`).on(`click.analytics`, trackClick);
// unbinds only click events in the "analytics" namespace
$.hook(`click-track`).off(`click.analytics`);
你也可以使用多個名稱空間:
// binds a click event in both the "analytics" and "usability" namespaces
$.hook(`click-track`).on(`click.analytics.usability`,trackClick);
// unbinds any events in either the "analytics" OR "usability" namespaces
$.hook(`click-track`).off(`.usability .analytics`);
// unbinds any events in both the "analytics" AND "usability" namespaces
$.hook(`click-track`).off(`.usability.analytics`);
需要注意的是,名稱空間的順序是沒有關係的,因為名稱空間不是層級式的。
如果你有一個複雜的功能需要多個元素繫結多個事件,那麼使用名稱空間是一種簡單的把他們組織起來然後快速清除的方式:
// free all elements on the page of any "analytics" event handling
$(`*`).off(`.analytics`);
名稱空間在寫外掛的時候尤其有用,因為這樣你就能保證只會取消自己名稱空間範圍內的事件處理函式的繫結。