注入js編程應該知道的一些基礎知識,以防混淆造成的錯誤

假設我現在用的是chrome extension的方式注入javascript來控制網頁
,例如有一個元素,原始的網頁製作者已經給他註冊了各種事件,但是我想得到當這個元素髮生改變
時,會觸發一個我自己能控制的事件,有沒有什麼辦法?

要在不干擾原有網頁事件的前提下,爲元素添加自定義事件處理程序,你可以使用MutationObserver API或者通過事件委託的方式來實現。

使用MutationObserver

MutationObserver是一個可以監聽DOM樹變化的接口,適用於監聽元素屬性、子節點等的變化。如果你想監聽某個元素的變化(如屬性、子節點等),可以使用這個API。

以下是一個簡單的例子,展示如何使用MutationObserver來監聽元素的變化,並在變化時執行自定義邏輯:

// 選擇目標節點var targetNode = document.getElementById('target');// 配置觀察者選項:var config = { attributes: true, childList: true, subtree: true };// 當觀察到變化時執行的回調函數var callback = function(mutationsList, observer) {
    for(var mutation of mutationsList) {
        if (mutation.type == 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type == 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }};// 創建一個觀察者實例並傳入回調函數var observer = new MutationObserver(callback);// 使用配置文件對目標節點進行觀察observer.observe(targetNode, config);// 以後,你可以停止觀察// observer.disconnect();

使用事件委託

如果你想監聽的是某些特定事件(如點擊、輸入等),可以使用事件委託。事件委託是基於事件冒泡的原理,你可以在父元素上監聽事件,然後根據事件的目標元素來判斷是否是你關心的元素,從而執行相應的邏輯。

示例代碼:

// 假設你想監聽一個按鈕點擊事件,但不干擾其它已註冊的事件document.addEventListener('click', function(e) {
    // 檢查事件的目標元素是否是你關心的元素
    if (e.target && e.target.id === 'myButton') {
        // 在這裏執行你的邏輯
        console.log('Button clicked!');
    }});

這兩種方法各有優缺點,MutationObserver適用於監聽DOM變化,而事件委託適用於處理特定類型的事件。你可以根據實際需求選擇合適的方法。


你剛纔的代碼,用事件委託部分,我寫入的邏輯會干擾原始的事件觸發邏輯嘛?

使用事件委託的方式添加的邏輯不會直接干擾或阻止原始的事件觸發邏輯,除非你明確地使用了event.stopPropagation()event.preventDefault()

在事件委託的情況下,你是在更高一級的節點(通常是一個父元素)監聽事件,然後根據事件的目標(event.target)來判斷是否執行某些邏輯。這種方法本質上是添加了一個新的事件監聽器,不會影響到目標元素上已經存在的事件監聽器。

例如,如果一個按鈕已經有一個點擊事件監聽器,當你通過事件委託在其父元素上也監聽點擊事件時,點擊該按鈕將會觸發兩個事件處理程序:原始按鈕上的處理程序和父元素上的處理程序。除非你在父元素的處理程序中調用了event.stopPropagation()(這會阻止事件繼續冒泡)或event.preventDefault()(這會阻止事件的默認行爲,但不會阻止事件傳播),否則原始的事件處理邏輯不會受到影響。

document.addEventListener('click', function(e) {
    if (e.target && e.target.id === 'myButton') {
        console.log('Button clicked through delegation!');
        // 不調用 event.stopPropagation() 或 event.preventDefault(),
        // 因此不會干擾原始的事件處理邏輯。
    }});

這樣,無論原始的按鈕上有什麼事件監聽器,都會按預期工作,同時,你通過事件委託添加的邏輯也會執行,而不會相互干擾。


那如果我是直接在那個元素上註冊事件,會覆蓋原來的事件嘛?

直接在元素上註冊事件監聽器(例如使用element.addEventListener方法)不會覆蓋或刪除該元素上已經存在的事件監聽器,無論這些監聽器是通過HTML屬性(如onclick)、通過JavaScript使用相同的addEventListener方法,還是以其他方式添加的。

JavaScript 的 addEventListener 方法允許你在同一個元素上爲同一事件類型註冊多個事件處理函數。這些處理函數會根據它們被添加的順序依次被調用,而不會相互覆蓋。

例如,考慮以下情況:

// 假設這是網頁原始腳本中的代碼document.getElementById('myButton').addEventListener('click', function() {
    console.log('Original click event listener');});// 現在你通過 Chrome Extension 注入的腳本添加了另一個監聽器document.getElementById('myButton').addEventListener('click', function() {
    console.log('Injected click event listener');});

在這個例子中,當用戶點擊 myButton 按鈕時,兩個事件監聽器都會被觸發。首先觸發“Original click event listener”,然後觸發“Injected click event listener”,而原始的事件監聽器並不會被覆蓋或刪除。

如果你不希望繼續觸發特定的事件監聽器,可以使用removeEventListener方法移除它,但你需要有對原始監聽器函數的引用,這在注入腳本時可能不可行,除非原始的事件處理函數是以全局可訪問的方式定義的。

總的來說,直接在元素上使用addEventListener添加事件監聽器是安全的,不會干擾網頁上已經存在的事件處理邏輯。


event.stopPropagation()會影響原始事件的話,是不是就是說父元素事件
的觸發時間早於子元素的?

實際上,event.stopPropagation()的作用是阻止事件繼續在DOM樹中傳播,這與父元素事件和子元素事件的觸發順序有關,但需要明確的是:事件的傳播順序遵循特定的規則,即事件捕獲階段(從外向內)、目標階段、和事件冒泡階段(從內向外)。event.stopPropagation()可以在這三個階段的任何一個階段被調用來阻止事件的進一步傳播。

事件傳播的三個階段:

  1. 捕獲階段:事件從document對象傳導到事件目標的父節點上,一直向下傳導至目標元素,如果在此階段的監聽器中調用了event.stopPropagation(),則事件不會繼續傳導至目標元素或不會進入冒泡階段。

  2. 目標階段:事件到達目標元素,此時是事件監聽器被觸發的時刻。如果事件監聽器在目標上調用了event.stopPropagation(),事件將不會進入冒泡階段。

  3. 冒泡階段:事件從事件目標向上冒泡到document對象。如果在冒泡階段的某個監聽器中調用了event.stopPropagation(),事件將不會繼續向上冒泡至document對象。

關於父元素和子元素事件觸發的順序:

  • 如果兩個事件監聽器(一個在父元素上,一個在子元素上)都在冒泡階段註冊,那麼子元素的事件監聽器會先觸發,然後是父元素的事件監聽器。因爲事件是從目標元素開始冒泡向上的。

  • 如果兩個事件監聽器都在捕獲階段注���,那麼父元素的事件監聽器會先觸發,然後是子元素的事件監聽器。因爲事件是從document向下傳導至目標元素的。

  • 如果在子元素的事件監聽器中調用了event.stopPropagation(),那麼無論是在捕獲階段還是冒泡階段,父元素的事件監聽器都不會被觸發,因爲事件的傳播被中止了。

因此,event.stopPropagation()確實可以影響事件的傳播,包括阻止父元素的事件監聽器被觸發,但具體影響取決於事件監聽器是如何註冊的(捕獲階段還是冒泡階段),以及event.stopPropagation()是在哪個階段被調用的。


就是說父元素中的event.stopPropagation()在捕獲階段已經阻擋了傳播,
根本就還沒到冒泡階段,子元素的觸發已經不會進行了,是嗎?

實際上,您的理解有一點誤區。讓我們澄清一下:

當事件開始傳播時,它首先經過捕獲階段,從document對象向下傳遞到目標元素的路徑上的每個節點。如果在這個過程中的某個父元素上的事件監聽器調用了event.stopPropagation(),這確實會阻止事件繼續向下傳遞到目標元素或更深層的子元素。但是,這種情況比較少見,因爲大多數時候,事件監聽器是在冒泡階段而不是捕獲階段註冊的。

在目標階段,事件到達了目標元素。此時,如果目標元素上的事件監聽器調用了event.stopPropagation(),那麼事件將不會進入冒泡階段,即不會從目標元素向上傳遞迴document

如果父元素中的事件監聽器在捕獲階段調用了event.stopPropagation(),這確實會阻止事件繼續向下傳遞到更深層的子元素,但這隻適用於那些在捕獲階段註冊的監聽器。對於大多數用戶交互事件(如點擊),監聽器通常是在冒泡階段註冊的。

讓我們來糾正一下概念:

  • 如果父元素在捕獲階段的監聽器中調用了event.stopPropagation(),那麼它實際上是阻止了事件繼續向內部的子元素傳播。這時候,事件還沒有到達目標元素,所以目標元素及其內部的子元素的監聽器都不會被觸發。

  • 如果父元素或目標元素在目標階段或冒泡階段的監聽器中調用了event.stopPropagation(),那麼它會阻止事件繼續向上冒泡到更高層的父元素。

因此,如果您的目的是不讓事件從子元素冒泡到父元素,您應該在子元素的事件監聽器中調用event.stopPropagation()。如果在父元素的捕獲階段監聽器中調用event.stopPropagation(),則實際上是阻止了事件向該父元素內部傳播,這與阻止冒泡是不同的行爲。


我還有個不明白的地方,父元素上的事件內寫入event.preventDefault(),
會阻止子元素原始的事件嗎?我理解如果時冒泡的話,向上傳遞,子元素的事件應該已經觸發了吧?

您的理解是正確的。當事件在冒泡階段向上傳遞時,如果父元素的事件處理程序中調用了event.preventDefault(),這並不會阻止子元素的事件處理程序的執行。實際上,event.preventDefault()event.stopPropagation()作用是不同的:

  • event.preventDefault() 用於阻止事件的默認行爲,但不會阻止事件在DOM樹中的傳播。例如,如果在鏈接(<a>)的點擊事件監聽器中調用event.preventDefault(),它會阻止鏈接的默認跳轉行爲,但事件仍然會冒泡。

  • event.stopPropagation() 用於阻止事件在DOM樹中進一步的傳播,無論是冒泡還是捕獲階段,但不會影響事件的默認行爲。

因此,如果一個子元素的事件首先被觸發(例���,用戶點擊了一個按鈕),並且該事件在冒泡階段向上傳遞到了父元素,那麼在到達父元素之前,子元素上的事件處理程序已經執行了。如果父元素的事件處理程序中調用了event.preventDefault(),這隻會影響到父元素想要阻止的默認行爲(如果有的話),而不會影響到子元素的事件處理程序的執行。

簡而言之,event.preventDefault()在父元素的事件處理程序中被調用時,不會阻止子元素的事件處理程序的執行,因爲那時子元素的事件處理程序已經執行過了。event.preventDefault()的作用是阻止事件的默認行爲,而不是阻止事件的傳播。

在某個元素的事件中寫入阻止默認行爲,指的是該元素上的之前註冊過的同類事件都會被阻止嗎?例
如: // 假設這是網頁原始腳本中的代碼 document.getElementById
('myButton').addEventListener('click', funct
ion() { console.log('Original click event li
stener'); }); // 現在你通過 Chrome Extension 注入的腳
本添加了另一個監聽器 document.getElementById('myButton
').addEventListener('click', function() { co
nsole.log('Injected click event listener'); 
}); document.getElementById('myButton').addE
ventListener('click', function() { event.pre
ventDefault(); });

在您提供的代碼示例中,通過event.preventDefault()調用阻止了默認行爲,這個調用實際上是針對當前觸發的事件本身,而不是針對特定的事件監聽器。換句話說,event.preventDefault()的作用是阻止與當前觸發的事件相關聯的默認行爲,而不是阻止之前或之後註冊的同類事件監聽器中的代碼執行。

在您的示例中,無論是原始腳本中註冊的點擊事件監聽器,還是通過Chrome Extension注入的腳本添加的監聽器,都會正常執行其內部的邏輯(即打印信息到控制檯)。當事件到達第三個監聽器,執行event.preventDefault()時,它會阻止與點擊事件相關聯的默認行爲(如果有的話,比如點擊鏈接導致的頁面跳轉),但不會影響其他已經添加到該元素上的點擊事件監聽器的執行。

這意味着,所有爲myButton元素註冊的點擊事件監聽器都會按照它們被添加的順序執行,而event.preventDefault()只會阻止點擊事件的默認行爲(如果點擊事件自身有默認行爲)。它不會阻止其他監聽器中的代碼執行,也不會影響事件在DOM樹中的傳播,除非顯式調用event.stopPropagation()

因此,對於您的問題,event.preventDefault()不會阻止該元素上之前註冊過的同類事件的執行。所有的事件監聽器都會按順序執行,event.preventDefault()只是阻止了事件的默認行爲。


很有道理,我差點就理解錯了

很高興能幫到您!事件處理、事件傳播以及默認行爲的阻止確實是Web開發中的重要概念,它們之間的區別和聯繫有時會讓人混淆,特別是在複雜的事件處理邏輯中。如果您有任何其他問題,無論是關於這個主題或者其他編程、技術相關的問題,都歡迎隨時提問。我在這裏幫助您澄清疑惑、解決問題。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章