如何移除事件監聽器

chuck發表於2023-03-16

在執行時清理你的程式碼是構建高效、可預測的應用程式,沒有商量餘地的部分。在JavaScript中,實現這一目標的方法之一是很好地管理事件監聽器,尤其是當不再需要時移除它們。

有好幾種方法可以做到這件事情,每種都有自己的一套權衡方法,使其在某些情況下更合適。我們將介紹幾種最常用的策略,以及當你試圖決定哪種方法最適合於任何特定時間的工作時,需要考慮的一些問題。

我們將對下面的設定進行修補--一個帶有單擊事件監聽器的按鈕:

<button id="button">Do Something</button>

<script>
document.getElementById('button').addEventListener('click', () => {
	console.log('clicked!');
});
</script>

使用getEventListeners()函式,你會看到只有一個監聽器連線到該元素:

getEventListeners.png

如果你需要移除該監聽器,你可以用以下幾個方法。

使用.removeEventListener()

這可能是最顯而易見的,但也是最有可能威脅到你心智的一個。.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()的once選項

如果.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手段。

使用AbortController()

該方法對我來說是新的。我是在看到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():如果你有一系列的監聽器想一次性地刪除,或者你只是喜歡這種語法時可以使用該方式。

以上就是本文的全部內容,如果對你有所幫助,歡迎點贊、收藏、轉發~

相關文章