JavaScript中5個重要的Observer函數(shù)小結(jié)
一、前言
瀏覽器為開發(fā)者提供了功能豐富的Observer
,在這篇文章中,我們將深入研究這些常見的瀏覽器 Observer
,剖析它們的作用、用法以及它們在 Web
開發(fā)中的應(yīng)用場景。
二、MutationObserver
MutationObserver
用于監(jiān)聽DOM
對象的變更(包括子節(jié)點),當(dāng)節(jié)點屬性發(fā)生變化,或執(zhí)行增刪改操作時執(zhí)行對應(yīng)的callback
。
MutationObserver
為我們提供了一種十分方便的監(jiān)聽DOM
變化的方式。
2.1、基本使用
// Observer需要一個用于監(jiān)聽的目標(biāo)DOM const targetNode = document.getElementById("app"); //用于確定mutation監(jiān)聽變化的范圍 const config = { attributes: true, // 監(jiān)聽目標(biāo)節(jié)點的屬性變化,例如id,class等屬性 childList: true, // 除目標(biāo)節(jié)點外還要監(jiān)聽目標(biāo)節(jié)點的直接子節(jié)點 subtree: true, // subtree的范圍大于childList,還包括子節(jié)點children characterData: true // 監(jiān)聽TextNode需要額外配置,默認TextNode變化不會觸發(fā)callback }; // 當(dāng)觀察到變動時執(zhí)行的回調(diào)函數(shù),mutationsList包含本次變更的信息 const callback = function (mutationsList, observer) { console.log(mutationsList) }; const observer = new MutationObserver(callback); observer.observe(targetNode, config);
2.2、API介紹
2.2.1、observe
observe
用于開啟對某個DOM
的監(jiān)聽,一個MutationObserver
可以通過多次調(diào)用observe
監(jiān)聽多個DOM
的變化。
當(dāng)變化發(fā)生時MutationObserver
會將一個或多個mutation
對象傳給callback
的第一個參數(shù),mutation
對象內(nèi)包含本次變更的相關(guān)信息下面看一下mutation
的結(jié)構(gòu)
{ addedNodes: [], //新增DOM時會包含被新增的DOM attributeName: "id", //本次變更的屬性名 attributeNamespace: null, //命名空間URI,一般用不到 nextSibling: null, //當(dāng)存在添加/刪除節(jié)點的操作時會存在nextSibling/previousSibling, 引用上一個/下一個兄弟節(jié)點 previousSibling: null, oldValue: null, removedNodes: [], target: Text, type: "characterData" //變更類型,如characterData,childList等 }
2.2.2、disconnect
observer.disconnect()
調(diào)用observer.disconnect
后Observer
將不再監(jiān)聽target
,如果不需要監(jiān)聽請及時調(diào)用該方法,以免產(chǎn)生預(yù)期之外的行為以及內(nèi)存泄漏。
2.2.3、takeRecords
takeRecords
用于獲取在事件隊列中但還未傳遞給callback
的mutation
對象,通常使用在調(diào)用disconnect
時又不想丟失之前的mutationRecords
(如果mutation
連續(xù)觸發(fā),可能出現(xiàn)mutation
還在隊列中但未傳遞給callback
的情況)。
2.3、常見場景
對于需要監(jiān)聽DOM
變化的場景可考慮使用MutationObserver
,利于用于Tag group
內(nèi)元素的動態(tài)渲染,下面使用MutationObserver
實現(xiàn)一個簡單的Todo List
2.3.1、源碼
<!DOCTYPE html> <html> <head> <title>MutationObserver To-Do List Demo</title> <style> #todo-list { list-style-type: none; } </style> </head> <body> <h1>待辦事項列表</h1> <ul id="todo-list"> <li>完成作業(yè)</li> <li>購物</li> </ul> <button id="addTask">添加任務(wù)</button> <button id="removeTask">移除任務(wù)</button> <p id="taskCount">任務(wù)數(shù)量:2</p> <script> const todoList = document.getElementById('todo-list'); const taskCount = document.getElementById('taskCount'); const observer = new MutationObserver((mutationsList, observer) => { mutationsList.forEach((mutation) => { if (mutation.type === 'childList') { updateTaskCount(); } }); }); const config = { childList: true }; observer.observe(todoList, config); document.getElementById('addTask').addEventListener('click', () => { const newTask = document.createElement('li'); newTask.textContent = '新任務(wù)'; todoList.appendChild(newTask); }); document.getElementById('removeTask').addEventListener('click', () => { const tasks = todoList.getElementsByTagName('li'); if (tasks.length > 0) { todoList.removeChild(tasks[0]); } }); function updateTaskCount() { const tasks = todoList.getElementsByTagName('li'); taskCount.textContent = `任務(wù)數(shù)量:${tasks.length}`; } </script> </body> </html>
2.3.2、預(yù)覽
在線源碼請點擊【前往】
2.3.3、效果
三、IntersectionObserver
IntersectionObserver
用于監(jiān)聽一個元素的可見比例(一個DOM
元素被另一個DOM
元素遮擋百分比)變化。
3.1、基本使用
const target = document.getElementById('app'); const options = { root: rootTarget, // 相對于某個元素進行遮擋計算 rootMargin: '0px', // 進行計算的邊界范圍,通過rootMargin可以實現(xiàn)提前計算或延遲計算(相對于root原本尺寸)的效果 threshold: 0.5 // 觸發(fā)callback時的遮擋比例,0.5代表元素被遮擋50%時觸發(fā)callback。由于瀏覽器事件循環(huán)機制的影響,callback觸發(fā)時遮擋比例通常不會是精確的50%。 }; const intersectionObserver = new IntersectionObserver((entries, observer) => { //和MutationObserver相同,也是產(chǎn)生一個array entries.forEach(entry => { console.log(entry) }); }, options); intersectionObserver.observe(target);
3.2、API介紹
3.2.1、observe & options
observe
方法用于啟動一個Observer
對DOM
元素的監(jiān)聽。在創(chuàng)建IntersectionObserver
時可以通過傳入option
改變監(jiān)聽的行為。
const options = { root: root, rootMargin: '100px', threshold: 0.7 };
在上面的配置中,通過配置rootMargin
為100px
在target
距離root
元素100px
時即可判定為被遮擋,通過threshold
設(shè)置為0.7
,當(dāng)遮擋比例查過70%
時執(zhí)行callback
。
3.2.2、entry
callback
第一個param
是entry
對象構(gòu)成的array
,entry
包含了觸發(fā)callback
時DOM
的位置信息
//被監(jiān)聽DOM元素的Rect信息 boundingClientRect: { bottom: 208 height: 200 left: 8 right: 208 top: 8 width: 200 x: 8 y: 8 } intersectionRatio: 1 //交叉比例 // 被監(jiān)聽元素與Root元素交叉部分矩形的Rect信息。 intersectionRect: { bottom: 208, height: 200, left: 8, right: 208, top: 8, width: 200, x: 8, y: 8 }, // 是否處于交叉狀態(tài) isIntersecting: true, isVisible: false, // Root元素的Rect信息 rootBounds: { bottom: 606, height: 606, left: 0, right: 476, top: 0, width: 476, x: 0, y: 0 }, // root元素 target: div#target, time: 49.09999990463257
3.3、常見場景
乍一看IntersectionObserver
好像沒啥用,單這個Api
在某些場景下十分好用。
比如我們有一個通過sticky
固定在屏幕頂部的header
元素,我們希望在觸發(fā)sticky
時給header
加一個shadow
(很多table
都有這樣的功能)
一種很常見的做法是監(jiān)聽scroll
,當(dāng)滾動一定距離時加上shadow
即可。但是監(jiān)聽scroll
本身會早成一定的渲染壓力(scroll
觸發(fā)非常頻繁),同時如果使用React
這樣的框架又會造成額外的render
,在用戶的視角看來更卡了。
此時使用IntersectionObserver
就很合適了,因為我們需要監(jiān)聽的只是觸發(fā)sticky
的一瞬間,其他的滾動都是無效的,沒必要進行計算。
3.3.1、源碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Sticky Header with Shadow on Intersection</title> <style> body { margin: 0; padding: 0; } header { height: 80px; background-color: #3498db; color: white; text-align: center; line-height: 80px; position: sticky; top: 0; z-index: 100; } .header-shadow { transition: box-shadow 0.3s ease; } .header-shadow.shadow { box-shadow: 0 2px 5px black; } section { height: 1000px; background-color: #ecf0f1; padding: 20px; } </style> </head> <body> <div id="guard"></div> <header id="sticky-header" class="header-shadow">Sticky Header</header> <section> <p>向下滾動觸發(fā)sticky時展示shadow</p> </section> <script> const header = document.getElementById('sticky-header'); const section = document.querySelector('section'); const options = { threshold: 1 }; //guard滾動到可視區(qū)域以外時認為觸發(fā)了shadow const intersectionObserver = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { header.classList.remove('shadow'); } else { header.classList.add('shadow'); } }); }, options); intersectionObserver.observe(document.getElementById('guard')); </script> </body> </html>
3.3.2、預(yù)覽
在線源碼請點擊【前往】
3.3.3、效果
四、ResizeObserver
ResizeObserver
是用于監(jiān)聽DOM
尺寸變化的observer
,當(dāng)DOM
尺寸變化是執(zhí)行callback
4.1、基本使用
和前面的api
用法差不多,這里不過多介紹。
const box = document.getElementById('box'); const resizeObserver = new ResizeObserver(entries => { entries.forEach(entry => { console.log(entry) }); }); resizeObserver.observe(box);
4.2、API介紹
entry
對象包含resize
相關(guān)的信息,下面看一下entry
的結(jié)構(gòu)
{ // 不同box-sizing下的尺寸 borderBoxSize: [{ blockSize: 200, inlineSize: 200, }], contentBoxSize: [{ blockSize: 200, inlineSize: 200, }], contentRect: { bottom: 200, height: 200, left: 0, right: 200, top: 0, width: 200, x: 0, y: 0 }, // 在物理設(shè)備像素上的大小, 在不同的屏幕上尺寸不同例如Retina devicePixelContentBoxSize: [{ blockSize: 300, inlineSize: 300 } ], target: div#resizable-box }
4.3、常見場景
可以基于ResizeObserver
實現(xiàn)一個簡單的resize-detector
(參考react-resize-detector
),在尺寸變化時返回尺寸信息,點擊盒子就算拖拽有效。
4.3.1、源碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ResizeObserver Demo with Resizable Box</title> <style> #resizable-box { width: 200px; height: 200px; background-color: #3498db; color: white; text-align: center; line-height: 200px; font-size: 24px; transition: background-color 0.5s ease; resize: both; overflow: auto; cursor: pointer; } </style> </head> <body> <div id="resizable-box">Resize me!</div> <script> const resizableBox = document.getElementById('resizable-box'); let isResizing = false; let startX, startY, startWidth, startHeight; const resizeObserver = new ResizeObserver(entries => { for (const entry of entries) { const { width, height } = entry.contentRect; console.log('寬度:', width, '高度:', height); } }); resizeObserver.observe(resizableBox); resizableBox.addEventListener('mousedown', startResize); document.addEventListener('mousemove', handleResize); document.addEventListener('mouseup', stopResize); function startResize(e) { isResizing = true; startX = e.clientX; startY = e.clientY; startWidth = parseInt(document.defaultView.getComputedStyle(resizableBox).width, 10); startHeight = parseInt(document.defaultView.getComputedStyle(resizableBox).height, 10); } function handleResize(e) { if (!isResizing) return; const newWidth = startWidth + (e.clientX - startX); const newHeight = startHeight + (e.clientY - startY); resizableBox.style.width = newWidth + 'px'; resizableBox.style.height = newHeight + 'px'; } function stopResize() { isResizing = false; } </script> </body> </html>
4.3.2、預(yù)覽
在線源碼請點擊【前往】
4.3.3、效果
五、PerformanceObserver
PerformanceObserver
用于監(jiān)聽瀏覽器的performance
事件,方便在performance
事件觸發(fā)時作統(tǒng)一處理。
5.1、基本使用
// mdn demo function perf_observer(list, observer) { console.log(list) } var observer2 = new PerformanceObserver(perf_observer); // entryTypes用于指定要監(jiān)聽的事件類型 observer2.observe({ entryTypes: ["measure"] });
5.2、API介紹
下面列一下常見的entryTypes
mark
:用于標(biāo)記時間戳的事件measure
:performance.measure
觸發(fā)的事件frame
:網(wǎng)頁渲染的事件navigation
:導(dǎo)航的事件,例如頁面加載或重新加載resource
:資源加載事件longtask
:長任務(wù)事件paint
:繪制事件,例如FP
,FCP
layout-shift
:用于監(jiān)視布局變化的事件
對于對性能比較敏感的項目以及長期性能監(jiān)控來說這個api
還是比較方便的。
六、ReportingObserver
ReportingObserver
用于監(jiān)聽瀏覽器報告的事件,例如廢棄API
,過時特性,網(wǎng)絡(luò)錯誤。做監(jiān)控SDK
的同學(xué)應(yīng)該經(jīng)常能用到,日常業(yè)務(wù)代碼用的比較少。
6.1、基本使用
這里就簡單看一下使用方法吧,比較簡單
const observer = new ReportingObserver((reports, observer) => { reports.forEach(report => { console.log(report); }); }); // 監(jiān)聽過時特性 observer.observe({ types: ['deprecation'] });
七、最后
到此這篇關(guān)于JavaScript中5個重要的Observer函數(shù)小結(jié)的文章就介紹到這了,更多相關(guān)JavaScript Observer內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!