如何監聽頁面 DOM 變動並高效響應

2021-01-11 TechWeb

最近在做 chrome 插件開發,既然是插件那就難免不對現有頁面做一些控制,比如事件監聽、調整布局、對 DOM 元素的增刪改查等等。其中有一個需求比較有意思,便整理一下順便把涉及到的知識點複習一遍。

需求是這樣的:在一個包含懶加載資源以及動態 DOM 元素生成的頁面中,需要針對頁面中存在的元素添加屬性顯示標籤。

從 DOM 變動事件監聽說起

首先假設大家已經知道 JavaScript 中事件的發生階段(捕獲-命中-冒泡),附上一張圖帶過這個內容,我們直接進入尋找解決方法的過程。

Graphical representation of an event dispatched in a DOM tree using the DOM event flow

開始的時候我一直在 window 狀態改變涉及到的事件中尋找,一圈搜尋下來發現也就 onload 事件最接近了,所以我們看看 MDN 對該事件的定義:

The load event is fired when a resource and its dependent resources have finished loading.

怎麼理解資源及其依賴資源已加載完畢呢?簡單來說,如果一個頁面涉及到圖片資源,那麼 onload 事件會在頁面完全載入(包括圖片、css文件等等)後觸發。一個簡單的監聽事件用 JavaScript 應該這樣書寫(注意不同環境下 load 和 onload 的差異):

<script>    window.addEventListener("load", function(event) {      console.log("All resources finished loading!");    });        // or    window.onload=function(){      console.log("All resources finished loading!");    };        // HTML  < body onload="SomeJavaScriptCode">        // jQuery    $( window ).on( "load", handler )  </script>  

當然,說到 onload 事件,有一個 jQuery 中相似的事件一定會被提及—— ready 事件。jQuery 中這樣定義這個事件:

Specify a function to execute when the DOM is fully loaded.

需要知道的是 jQuery 定義的 ready 事件實質上是為 DOMContentLoaded 事件設計的,所以當我們談論加載時應該區分的事件其實是 onload(接口 UIEvent) 以及 DOMContentLoaded(接口 Event),MDN 這樣描述 DOMContentLoaded:

當初始HTML文檔被完全加載和解析時,DOMContentLoaded 事件被觸發,而無需等待樣式表、圖像和子框架完成加載。另一個不同的事件 load 應該僅用於檢測一個完全加載的頁面。

所以可以知道,當一個頁面加載時應先觸發 DOMContentLoaded 然後才是 onload. 類似的事件及區別包括以下幾類:

DOMContentLoaded: 當初始HTML文檔被完全加載和解析時,DOMContentLoaded 事件被觸發,而無需等待樣式表、圖像和子框架完成加載; readystatechange: 一個document 的 Document.readyState 屬性描述了文檔的加載狀態,當這個狀態發生了變化,就會觸發該事件; load: 當一個資源及其依賴資源已完成加載時,將觸發load事件; beforeunload: 當瀏覽器窗口,文檔或其資源將要卸載時,會觸發beforeunload事件。 unload: 當文檔或一個子資源正在被卸載時, 觸發 unload事件。

細心點會發現上面在介紹事件時提到了 UIEvent 以及 Event,這是什麼呢?這些都是事件——可以被 JavaScript 偵測到的行為。其他的事件接口還包括 KeyboardEvent / VRDisplayEvent (是的,沒錯,這就是你感興趣且熟知的那個 VR)等等;如果在搜尋引擎中稍加搜索,你會發現有些資料裡寫到事件可以分為以下幾類:

UI事件 焦點事件 滑鼠與滾輪事件 鍵盤與文本事件 複合事件 變動事件 HTML5 事件 設備事件 觸摸與手勢事件

但這樣寫實在有些凌亂,其中一些是 DOM3 定義的事件,有一些是單獨列出的事件,如果你覺得熟悉那麼你會發現這是 JavaScript 高級程序設計裡的敘述模式,在我看來,理解這些事件可以按照 DOM3 事件以及其他事件來做區分:其中,DOM3 級事件規定了以下幾類事件 – UI 事件, 焦點事件, 滑鼠事件, 滾輪事件, 文本事件, 鍵盤事件, 合成事件, 變動事件, 變動名稱事件; 而剩下的例如 HTML5 事件可以單獨做了解。而剛開始提到的 Event 作為一個主要接口,是很多事件的實現父類。有關 Web API 接口可以在這裡查到,裡面可以看到有很多 Event 字眼。

好吧,事件說了這麼多,我們還是沒有解決剛開始提出的問題,如果監聽頁面中動態生成的元素呢?想到動態生成的元素都是需要通過網絡請求獲取資源的,那麼是否可以監聽所有 HTTP 請求呢?查看 jQuery 文檔可以知道每當一個Ajax請求完成,jQuery 就會觸發 ajaxComplete 事件,在這個時間點所有處理函數會使用 .ajaxComplete() 方法註冊並執行。但是誰能保證所有 ajax 都從 jQuery 走呢?所以應該在變動事件中做出選擇,我們來看看 DOM2 定義的如下變動事件:

DOMSubtreeModified: 在DOM結構發生任何變化的時候。這個事件在其他事件觸發後都會觸發; DOMNodeInserted: 當一個節點作為子節點被插入到另一個節點中時觸發; DOMNodeRemoved: 在節點從其父節點中移除時觸發; DOMNodeInsertedIntoDocument: 在一個節點被直接插入文檔或通過子樹間接插入文檔之後觸發。這個事件在 DOMNodeInserted 之後觸發; DOMNodeRemovedFromDocument: 在一個節點被直接從文檔移除或通過子樹間接從文檔移除之前觸發。這個事件在 DOMNodeRemoved 之後觸發; DOMAttrModified: 在特性被修改之後觸發; DOMCharacterDataModified: 在文本節點的值發生變化時觸發;

所以,用 DOMSubtreeModified 好像沒錯。師兄旁邊提醒,用 MutationObserver, 於是又搜到了一個新大陸。MDN 這樣描述 MutationObserver:

MutationObserver給開發者們提供了一種能在某個範圍內的DOM樹發生變化時作出適當反應的能力.該API設計用來替換掉在DOM3事件規範中引入的Mutation事件.

DOM3 事件規範中的 Mutation 事件可以被簡單看成是 DOM2 事件規範中定義的 Mutation 事件的一個擴展,但是這些都不重要了,因為他們都要被 MutationObserver 替代了。好了,那麼來詳細介紹一下 MutationObserver 吧。文章《Mutation Observer API》對 MutationObserver 的用法介紹的比較詳細,所以我挑幾點能直接解決我們需求的說一說。

既然要監聽 DOM 的變化,我們來看看 Observer 的作用都有哪些:

它等待所有腳本任務完成後,才會運行,即採用異步方式。

它把 DOM 變動記錄封裝成一個數組進行處理,而不是一條條地個別處理 DOM 變動。

它既可以觀察發生在 DOM 的所有類型變動,也可以觀察某一類變動。

MutationObserver 的構造函數比較簡單,傳入一個回調函數即可(回調函數接受兩個參數,第一個是變動數組,第二個是觀察器實例):

let observer = new MutationObserver(callback); 

觀察器實例使用 observe 方法來監聽, disconnect 方法停止監聽,takeRecords 方法來清除變動記錄。

let article = document.body;     let  options = {    'childList': true,    'attributes':true  } ;     observer.observe(article, options);  

observe 方法中第一個參數是所要觀察的變動 DOM 元素,第二個參數則接收所要觀察的變動類型(子節點變動和屬性變動)。變動類型包括以下幾種:

childList:子節點的變動。 attributes:屬性的變動。 characterData:節點內容或節點文本的變動。 subtree:所有後代節點的變動。

想要觀察哪一種變動類型,就在 option 對象中指定它的值為 true。需要注意的是,如果設置觀察 subtree 的變動,必須同時指定 childList、attributes 和 characterData 中的一種或多種。disconnect 方法和 takeRecords 方法則直接調用即可,無傳入參數。

好的,我們已經搞定了 DOM 變動的監聽,將代碼刷新一下看下效果吧,因為頁面由很多動態生成的商品組成,那麼我應該在 body 上添加變動監聽,所以 options 應該這樣設置:

var options = {    'attributes': true,    'subtree': true  }  

咦?頁面往下拉一小點就觸發了 observer 幾十次?這樣 DOM 哪吃得消啊,查看了頁面的變動記錄發現每次新進的資源底層都調用了 Node.insertBefore() 方法…

再聊聊 JavaScript 中的截流/節流函數

現在遇到的一個麻煩是, DOM 變動太頻繁了,如果每次變動都監聽那真是太耗費資源了。一個簡單的解決辦法是我就放棄監聽了,而採用 setInterval 方法定時執行更新邏輯。是的,雖然方法原始了一點,但是性能上比 Observer 「改進」了不少。

這個時候,又來了師兄的助攻:「用用截流函數」。記起之前看《JavaScript 語言精粹》的時候看到是用 setTimeout 方法自調用來解決 setInteval 的頻繁執行吃資源的現象,不知道兩者是不是有關聯。網上一查發現有兩個「jie流函數」。需求來自於這裡:

在前端開發中,頁面有時會綁定scroll或resize事件等頻繁觸發的事件,也就意味著在正常的操作之內,會多次調用綁定的程序,然而有些時候javascript需要處理的事情特別多,頻繁出發就會導致性能下降、成頁面卡頓甚至是瀏覽器奔潰。

如果重複利用 setTimeout 和 clearTimeout 方法,我們好像可以解決這個頻繁觸發的執行。每次事件觸發的時候我首先判斷一下當前有沒有一個 setTimeout 定時器,如果有的話我們先將它清除,然後再新建一個 setTimeout 定時器來延遲我的響應行為。這樣聽上去還不錯,因為我們每次都不立即執行我們的響應,而頻繁觸發過程我們又能保持響應函數一直存在(且只存在一個),除了會有些延遲響應外,沒什麼不好的。是的這就是截流函數(debounce),有一篇博客用這個小故事介紹它:

形像的比喻是橡皮球。如果手指按住橡皮球不放,它就一直受力,不能反彈起來,直到鬆手。debounce 的關注點是空閒的間隔時間。

在我的業務中,在 observer 實例中調用下面寫的這個截流函數就可以啦

/**  * fn 執行函數  * context 綁定上下文  * timeout 延時數值  **/  let debounce = function(fn, context, timeout) {  let timer;            // 利用閉包將內容傳遞出去  return function() {    if (timer) {      // 清除定時器      clearTimeout(timer);    }    // 設置一個新的定時器    timer = setTimeout(function(){    fn.apply(context, arguments)    }, timeout);   }  }  

當然,解決了自己的問題,但還有一個概念沒有說到——「節流函數」。同一篇博文裡也使用了一個例子來說明它:

形像的比喻是水龍頭或機槍,你可以控制它的流量或頻率。throttle 的關注點是連續的執行間隔時間。

函數節流的原理也挺簡單,一樣還是定時器。當我觸發一個時間時,先setTimout讓這個事件延遲一會再執行,如果在這個時間間隔內又觸發了事件,那我們就清除原來的定時器,再setTimeout一個新的定時器延遲一會執行。函數節流的出發點,就是讓一個函數不要執行得太頻繁,減少一些過快的調用來節流。這裡用 AlloyTeam 的節流代碼實現來解釋:

// 參數同上  var throttle = function(fn, delay, mustRunDelay){   var timer = null;   var t_start;   return function(){      var context = this, args = arguments, t_curr = +new Date();            // 清除定時器      clearTimeout(timer);            // 函數初始化判斷      if(!t_start){          t_start = t_curr;      }            // 超時(指定的時間間隔)判斷      if(t_curr - t_start >= mustRunDelay){          fn.apply(context, args);          t_start = t_curr;      }      else {          timer = setTimeout(function(){              fn.apply(context, args);          }, delay);      }   };  };  

當然,AlloyTeam 那篇文章將這裡所說的截流函數作為節流函數的 V1.0 版本,你也可以這樣認為。畢竟,設置了必然觸發執行的時間間隔(即 mustRunDelay 函數),可以使得截流函數不會在「瘋狂事件」情況下無止境的循環下去。

Observer 和截流函數一結合,問題解決啦嘿嘿。當然還有很多坑,下次再開一篇說說吧。

參考

https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded https://developer.mozilla.org/zh-CN/docs/Web/Events/load https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener http://www.cnblogs.com/fsjohnhuang/p/4147810.html http://www.alloyteam.com/2012/11/javascript-throttle/

點讚 0

相關焦點

  • MutationObserver: dom變化監聽 給你代碼
    如何來監聽dom變化?工作中突然有一個需求,必須你要監聽dom的插入,那麼就需要去監聽dom變化。在一般印象中,監聽事件應該是這麼實現的比如 xx.addEventListener(xxx)。而實際上關於這個dom變化有一套html5標準。
  • DOM破壞攻擊學習
    0x01 簡介DOM最初誕生的時候沒有一個很好的標準,以至於各個瀏覽器在實現的過程中會支持DOM的一些怪異行為,而這些行為可能會導致DOM Clobbering的發生瀏覽器可能會將各種DOM元素的name和id屬性添加為document的屬性或頁面的全局變量,這會導致覆蓋掉document原有的屬性或全局變量,或者劫持一些變量的內容。
  • HTML文檔的訪問埠DOM (Document Object Model)
    第五節 HTML文檔的訪問埠DOM (Document Object Model)大家好,我們繼續對HTML文檔進行學習,在前幾節中我們認識了網頁文檔上面的各種元素,那麼我們又該怎麼訪問他們呢,是如何實現呢?其實,HTML文檔,提供了訪問其元素的埠,這就是HTML DOM,其定義了一套標準的針對 HTML 文檔的對象訪問或操作的機制。
  • 微型SSD與IVB完美結合 RST智能響應技術實測
    第1頁RST智能響應技術實測  如何提高系統運行速度?這個問題已經成為一個迫在眉睫的問題。使用SSD固態硬碟雖然不失為最佳選擇,但是SSD的最大劣勢在於較低的容量,使用戶在使用時並不能完全體驗到革命性的速度提升,速度與容量仿佛在近期成為了矛盾的體現。而Intel為我們提供了另一種方案,在一定程度上解決了容量與速度之間的矛盾,即RST技術。
  • 被監聽7年一無所知!美大使做夢都想不到,木質雕塑竟是竊聽器
    當時最開始發明出這種竊聽器的是一名俄國人,該竊聽器被發明出來以後開始用於監聽美國大使,從1945年該竊聽器正式投入使用,駐俄美國大使被監聽了七年的時間卻一無所知,畢竟他們做夢也想不到,擺在辦公室裡的那個木質雕塑竟然會是竊聽器,這聽起來實在是太不可思議了。
  • FBI監聽電話欠費遭美電信公司停機
    通常,聯邦調查局從政府獲得的秘密調查資金用於為僱員租車、租房子、監聽等。但聯邦調查局承認,管理全國56個分部秘密調查資金的財務系統已經「陳舊」。    由於簿記存在漏洞,審計人員查出一名前僱員盜用公款2.5萬美元。這名僱員已於2006年6月被定罪。此外,聯邦調查局數家分部自己支付了本應由總部承擔的秘密調查費用。
  • 中國移動技術專家:「製作手機監聽卡」純屬騙局
    新華網北京6月4日電(記者劉菊花)北京海澱警方近日通報,首次打掉「製作手機監聽卡」詐騙團夥。中國移動技術專家4日表示,SIM卡無卡遠程複製是不可能的,網絡上和簡訊中關於「製作手機監聽卡」的廣告純屬欺詐,同時提醒廣大客戶要保護好自己的SIM卡。
  • BDSM大型科普:Dom分型之「白騎士」,混圈必備知識
    在Dom/Sub人群中,有兩類人經常會不自覺地互相吸引而在一起,一類是受過心理創傷轉而投身BDSM中試圖尋求庇護的sub,被稱為DID(damsel in distress),另一類則是專以治癒他人、幫助sub走出低谷為樂,如果你沒點傷痛我反而不開心的dom,他們有個很好聽的名字——「白騎士」(White Knight)。
  • 直播頁面
    所以我們在有條件的醫療機構都配置了發熱門診專屬CT,前期配了21個,最近又配了14個,後續還要專門完善,這對擴大發熱門診的應急響應能力非常重要。在一級響應後,我們實踐中借鑑其他國家的經驗,在原來全市的發熱門診基礎上在182個社區建立了發熱哨點門診,因為抗疫過程中社區是非常重要的,上海的三級診療體系中社區的服務量始終佔很大比例,如何在社區就診中篩查出一部分發熱病人,對於防控也是非常重要。
  • 如何正確理解 RT 並監控 MySQL 的響應時間
    app <---->(網絡建立連接,data 傳輸)<----> proxy <---->(網絡建立連接,data 傳輸)<----> mysql(執行)因為網絡問題丟包,重傳等導致數據傳輸時間增加,進而導致總體的 RT 時間增加 ,還有常見的案例 app 伺服器 cpu 飆高導致程序執行的速度變慢,JAVA 程序 GC 等因素也會導致
  • 美國FBI電話監聽有絕招 分支機構遍布全球(圖)
    為讓這些武器走私者現出原形,FBI調查人員先後跟著「線人」來到南非、亞美尼亞及喬治亞,並在7部電話上安裝竊聽裝置,監聽了雙方1.5萬次電話交談。在調查過程中為博得對方信任,這名「線人」還從對方手中購買了8件攻擊性武器。歷時一年多的接觸後,FBI終於等到機會,在紐約、洛杉磯和邁阿密分三路逮捕了18名涉嫌走私重型武器入境的嫌疑犯。不過FBI用「線人」破案也有出格的時候。
  • 響應客戶需求 金百澤柔性製造增添實力
    根據生產面積的不同,PCB分為樣板、小批量板、中批量板和大批量板,其中樣板、小批量板有著訂單種類多、面積小、交期短的特點,這些特點大幅加劇了生產過程中的隨機性和複雜性,不僅對生產製造本身的計劃、實施、控制和管理的要求越來越高,而且需要更加高效地組織要素之間的協作,具備更高的生產難度和管理難度,只有具備豐富生產經驗與雄厚技術實力的企業才能勝任。
  • 喜馬拉雅上線大字模式,響應網際網路適老化改造
    喜馬拉雅積極響應行動倡議,從長輩用戶不斷增長的需求出發,在平臺交互界面、功能、內容、場景等多個維度上進行全新的開發與優化。即日起,當用戶打開喜馬拉雅,在個人帳戶的「必備工具」中,就能看到大字模式入口,一點即可開啟。從設計上看,大字模式字體更適合長輩瀏覽,頁面更加簡潔清晰。值得注意的是,頁面內沒有廣告設計,不用擔心長輩誤點,減輕用戶上手的難度。
  • 我國電力需求響應發展現狀及如何參與電力市場交易
    我國電力需求響應發展現狀及如何參與電力市場交易 發布時間: 2020-11-30 11:14:35   來源:《中國電力》  作者:張高、薛松
  • 魅族15 Plus宣傳頁面偷跑 「美人尖」顯眼
    今天,網絡上再次流出一張疑似魅族15 Plus的宣傳頁面圖。網傳魅族15 Plus宣傳頁面如上圖,「15 Plus」的字樣赫然顯示在頁面醒目位置,下面有一段文字: 15 探索與追求,今天,我們回來了。不出意外的話,這段文字中間應該少了一個「年」字。因此,這個宣傳頁面的真實性不高,畢竟官方不可能犯如此低級的錯誤。
  • 高效軟體推薦,加文字內容選它
    點擊軟體頁面上方菜單欄中的【文件】按鍵,接著點擊其下拉欄中的【打開項目】按鍵,把一些相關的視頻文件給添加進來。打開所需視頻文件後,我們便可以進入到下一個步驟。第二步:選中視頻軌道,剪輯視頻內容大家可以看到,頁面左上方提供有多種視頻素材選項。給視頻加上合適文字內容,可以使視頻內容更加豐富。想要對視頻進行剪輯操作的話,小夥伴們點擊、使用頁面中間一欄的視頻剪輯工具圖標即可。
  • 高效語音轉文字,學會這幾招,讓音頻轉文字變得簡單
    1、微信語音轉文字準備工具:手機、微信、良好網絡數據我們先要說的是微信語音轉文字,其實微信語音轉文字可以理解為實時錄音轉文字,邊錄音邊轉換;操作方法:打開微信和別人的聊天對話框,在輸入框右邊有個「+」號,頁面跳轉之後,選擇頁面的「語音輸入」就可以邊說話邊轉換成文字了。
  • 遭以色列監聽 - 現代快報多媒體數字報刊平臺
    在提及美國國務院的一號人物克裡的電話是如何被竊聽時,《明鏡周刊》則引述多個情報機構資深消息人士的分析指出,克裡在當時雖然使用了擁有加密功能的電話,但他也使用了極易遭監聽的普通衛星電話,因而這就使通話內容遭到了以色列情報機構的成功竊聽。  情報界人士還表示說:「當克裡在旅行時,他總是使用衛星電話,那麼他打電話的內容就十分容易遭到各方的竊聽了。」
  • 地質災害風險高,迎戰「摩羯」浙江啟動地質災害IV級應急響應
    浙江在線8月12日訊(浙江在線記者 方臻子)記者從浙江省國土資源廳獲悉,根據《浙江省突發地質災害應急預案》,浙江省地質災害防治工作聯席會議辦公室決定:自2018年8月12日9時起啟動地質災害IV級應急響應
  • 批量上傳:別讓一鍵高效工具成為「導入失敗」的警報器
    一、明確的導入操作指引,減輕用戶學習成本提供下載導入模板和導入模板的入口;下載和導入的模板入口在一個頁面,避免出現點擊「導入」,還要花時間找導入模板的情況。常規設計如圖:(提供下載和導入的模板)二、 欄位/字符詳細說明,將導入失敗的風險扼殺在模板設計中1.