在執行時清理你的程式碼是構建高效、可預測的應用程式,沒有商量餘地的部分。在JavaScript中,實現這一目標的方法之一是很好地管理事件監聽器,尤其是當不再需要時移除它們。
有好幾種方法可以做到這件事情,每種都有自己的一套權衡方法,使其在某些情況下更合適。我們將介紹幾種最常用的策略,以及當你試圖決定哪種方法最適合於任何特定時間的工作時,需要考慮的一些問題。
我們將對下面的設定進行修補--一個帶有單擊事件監聽器的按鈕:
<button id="button">Do Something</button>
<script>
document.getElementById('button').addEventListener('click', () => {
console.log('clicked!');
});
</script>
使用getEventListeners()
函數,你會看到只有一個監聽器連線到該元素:
如果你需要移除該監聽器,你可以用以下幾個方法。
這可能是最顯而易見的,但也是最有可能威脅到你心智的一個。.removeEventListener()
方法接收三個引數:待移除監聽器的型別,監聽器的回撥函數,以及可選物件。
但這裡有一個(潛在的)棘手的部分:這些確切的引數必須與設定監聽器時使用的引數完全一致,包括記憶體中回撥的相同參照。否則,.removeEventListener()
啥也不做。
考慮到這一點,下面的範例將是完全無效的:
document.getElementById('button').addEventListener('click', () => {
console.log('clicked!');
});
document.getElementById('button').removeEventListener('click', () => {
console.log('clicked!');
});
儘管回撥函數看起來一樣,但它們不是相同的參照。解決方案是將回撥函數設定為一個變數,並在.addEventListener()
和.removeEventListener()
中參照它。
const myCallback = () => {
console.log('clicked!');
};
document.getElementById('button').addEventListener('click', myCallback);
document.getElementById('button').removeEventListener('click', myCallback);
或者,對於特定的用例,你也可以通過在函數本身中參照一個偽匿名函數來移除監聽器:
document
.getElementById('button')
.addEventListener('click', function myCallback() {
console.log('clicked!');
this.removeEventListener('click', myCallback);
});
儘管有其特殊性,.removeEventListener()
的優勢在於其目的非常明確。當你通讀完程式碼時,對它的作用沒有任何疑問。
如果.addEventListener()
是為了一次性使用,.addEventListener()
方法自帶一個工具可以幫助自己清理:once
選項。這和它聽起來一樣簡單。如果設定為true
,監聽器會在第一次被呼叫後自動移除它自己:
const button = document.getElementById('button');
button.addEventListener('click', () => {
console.log('clicked!');
}, { once: true });
// 'clicked!'
button.click();
// No more listeners!
getEventListeners(button) // {}
假設它符合你的使用情況,如果你熱衷於使用匿名函數,這種方法可能是合適的,因為你的監聽器只需要被呼叫一次。
有時,你不知道某個節點上所有活躍的監聽器,但你知道你想要摧毀它們。在這種情況下,克隆整個節點並使用克隆的替換該節點是可行的。使用.cloneNode()
方法,通過.addEventListener()
附加的監聽器都不會被帶過去,給它一個乾淨的環境。
讓我們回到使用者端JavaScript的石器時代,你會看到這是由查詢到父節點,然後用一個克隆節點替換一個特定的子節點完成的:
button.parentNode.replaceChild(button.cloneNode(true), button);
但在現代瀏覽器中,可以使用.replaceWith()
進行簡化:
button.replaceWith(button.cloneNode(true));
有一件事可能會讓你感到困惑,那就是內部監聽器會被保留下來,這意味著一個帶有onclick
屬性的按鈕仍然會按照定義觸發:
<button id="button" onclick="console.log('clicked!')">
Do Something
</button>
總之,如果你需要用蠻力不分青紅皁白地刪除任何種類的監聽器,這是一個值得一試的選擇。然而,在缺點方面,就是它的目的不太明顯。有人會說它是一個hack手段。
該方法對我來說是新的。我是在看到Caleb Porzio的這條推文時才知道的。如果你和我一樣,你可能只聽說過AbortController
是用來取消fetch()
請求的。但顯然,它比這更靈活。
最近,.addEventListener()
可以設定一個signal
,用於終止/移除一個監聽器。當相應的控制器呼叫.abort()
時,該訊號將觸發監聽器被刪除:
const button = document.getElementById('button');
const controller = new AbortController();
const { signal } = controller;
button.addEventListener('click', () => console.log('clicked!'), { signal });
// Remove the listener!
controller.abort();
這樣做最明顯的好處可能是符合人體工程學。它(在我看來)是一種更清晰的移除監聽器的方式,而不用處理.removeEventListener()
的潛在麻煩。但也有一個更具戰術性的優勢:你可以使用一個訊號來一次性移除多個任何型別的監聽器。而且使用匿名函數也是完全可以的:
const button = document.getElementById('button');
const controller = new AbortController();
const { signal } = controller;
button.addEventListener('click', () => console.log('clicked!'), { signal });
window.addEventListener('resize', () => console.log('resized!'), { signal });
document.addEventListener('keyup', () => console.log('pressed!'), { signal });
// Remove all listeners at once:
controller.abort();
唯一讓人猶豫不決的原因是瀏覽器支援。這是一個相對較新的功能,自2021年(v90)以來,Chrome瀏覽器才全面支援。因此,如果你需要支援超過有幾年歷史的瀏覽器版本,請記住這一點。
跟其他事情一樣,這取決於實際使用場景:
.removeEventListener()
:如果回撥函數被賦值給一個變數,並且在監聽器被新增的地方很容易找到時可以使用該方式。once
選項:如果你只需要觸發一次回撥時,可以使用該方式。AbortController()
:如果你有一系列的監聽器想一次性地刪除,或者你只是喜歡這種語法時可以使用該方式。以上就是本文的全部內容,如果對你有所幫助,歡迎點贊、收藏、轉發~