1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        JavaScript 中5個(gè)重要的Observer函數(shù),你知道幾個(gè)?

        共 17154字,需瀏覽 35分鐘

         ·

        2024-03-22 13:30

            

        作者:MapleSyrupx

        https://juejin.cn/post/7302344328243773450

        前言

        瀏覽器為開(kāi)發(fā)者提供了功能豐富的Observer,在這篇文章中,我們將深入研究這些常見(jiàn)的瀏覽器 Observer,剖析它們的作用、用法以及它們?cè)?Web 開(kāi)發(fā)中的應(yīng)用場(chǎng)景。

        MutationObserver

        MutationObserver用于監(jiān)聽(tīng)DOM對(duì)象的變更(包括子節(jié)點(diǎn)),當(dāng)節(jié)點(diǎn)屬性發(fā)生變化,或執(zhí)行增刪改操作時(shí)執(zhí)行對(duì)應(yīng)的callback。

        MutationObserver為我們提供了一種十分方便的監(jiān)聽(tīng)DOM變化的方式。

        基本使用

            // Observer需要一個(gè)用于監(jiān)聽(tīng)的目標(biāo)DOM
        const targetNode = document.getElementById("app");

        //用于確定mutation監(jiān)聽(tīng)變化的范圍
        const config = { 
          attributestrue// 監(jiān)聽(tīng)目標(biāo)節(jié)點(diǎn)的屬性變化,例如id,class等屬性
          childListtrue// 除目標(biāo)節(jié)點(diǎn)外還要監(jiān)聽(tīng)目標(biāo)節(jié)點(diǎn)的直接子節(jié)點(diǎn)
          subtreetrue,  // subtree的范圍大于childList,還包括子節(jié)點(diǎn)children
          characterDatatrue   // 監(jiān)聽(tīng)TextNode需要額外配置,默認(rèn)TextNode變化不會(huì)觸發(fā)callback
        };

        // 當(dāng)觀察到變動(dòng)時(shí)執(zhí)行的回調(diào)函數(shù),mutationsList包含本次變更的信息
        const callback = function (mutationsList, observer{
          console.log(mutationsList)
        };

        const observer = new MutationObserver(callback);
        observer.observe(targetNode, config);

        API介紹

        observe

        observe用于開(kāi)啟對(duì)某個(gè)DOM的監(jiān)聽(tīng),一個(gè)MutationObserver可以通過(guò)多次調(diào)用observe監(jiān)聽(tīng)多個(gè)DOM的變化。

        當(dāng)變化發(fā)生時(shí)MutationObserver會(huì)將一個(gè)或多個(gè)mutation對(duì)象傳給callback的第一個(gè)參數(shù),mutation對(duì)象內(nèi)包含本次變更的相關(guān)信息下面看一下mutation的結(jié)構(gòu)

            {
          addedNodes: [],  //新增DOM時(shí)會(huì)包含被新增的DOM
          attributeName"id",   //本次變更的屬性名
          attributeNamespacenull,  //命名空間URI,一般用不到
          nextSiblingnull//當(dāng)存在添加/刪除節(jié)點(diǎn)的操作時(shí)會(huì)存在nextSibling/previousSibling, 引用上一個(gè)/下一個(gè)兄弟節(jié)點(diǎn)
          previousSiblingnull,
          oldValuenull,
          removedNodes: [],
          target: Text,
          type"characterData" //變更類(lèi)型,如characterData,childList等
        }

        takeRecords

        takeRecords用于獲取在事件隊(duì)列中但還未傳遞給callback的mutation對(duì)象,通常使用在調(diào)用disconnect時(shí)又不想丟失之前的mutationRecords(如果mutation連續(xù)觸發(fā),可能出現(xiàn)mutation還在隊(duì)列中但未傳遞給callback的情況)。

        disconnect

            javascript
        復(fù)制代碼
        observer.disconnect()

        調(diào)用observer.disconnect后Observer將不再監(jiān)聽(tīng)target,如果不需要監(jiān)聽(tīng)請(qǐng)及時(shí)調(diào)用該方法,以免產(chǎn)生預(yù)期之外的行為以及內(nèi)存泄漏。

        常見(jiàn)場(chǎng)景

        對(duì)于需要監(jiān)聽(tīng)DOM變化的場(chǎng)景可考慮使用MutationObserver,利于用于Tag group內(nèi)元素的動(dòng)態(tài)渲染,下面使用MutationObserver實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Todo List

            <!DOCTYPE html>
        <html>
        <head>
          <title>MutationObserver To-Do List Demo</title>
          <style>
            #todo-list {
              list-style-type: none;
            }
          
        </style>
        </head>
        <body>
          <h1>待辦事項(xiàng)列表</h1>

          <ul id="todo-list">
            <li>完成作業(yè)</li>
            <li>購(gòu)物</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 = { childListtrue };

            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>
        IntersectionObserver

        IntersectionObserver用于監(jiān)聽(tīng)一個(gè)元素的可見(jiàn)比例(一個(gè)DOM元素被另一個(gè)DOM元素遮擋百分比)變化。

        基本使用

            const target = document.getElementById('app');

        const options = {
          root: rootTarget, // 相對(duì)于某個(gè)元素進(jìn)行遮擋計(jì)算
          rootMargin'0px'// 進(jìn)行計(jì)算的邊界范圍,通過(guò)rootMargin可以實(shí)現(xiàn)提前計(jì)算或延遲計(jì)算(相對(duì)于root原本尺寸)的效果
          threshold0.5 // 觸發(fā)callback時(shí)的遮擋比例,0.5代表元素被遮擋50%時(shí)觸發(fā)callback。由于瀏覽器事件循環(huán)機(jī)制的影響,callback觸發(fā)時(shí)遮擋比例通常不會(huì)是精確的50%。
        };

        const intersectionObserver = new IntersectionObserver((entries, observer) => {
          //和MutationObserver相同,也是產(chǎn)生一個(gè)array
          entries.forEach(entry => {
            console.log(entry)
          });
        }, options);

        intersectionObserver.observe(target);

        API介紹

        observe & options

        observe方法用于啟動(dòng)一個(gè)Observer對(duì)DOM元素的監(jiān)聽(tīng)。在創(chuàng)建IntersectionObserver時(shí)可以通過(guò)傳入option改變監(jiān)聽(tīng)的行為。

            const options = {
          root: root, 
          rootMargin'100px'
          threshold0.7 
        };

        在上面的配置中,通過(guò)配置rootMargin為100px在target距離root元素100px時(shí)即可判定為被遮擋,通過(guò)threshold設(shè)置為0.7,當(dāng)遮擋比例查過(guò)70%時(shí)執(zhí)行callback。

        entry

        callback第一個(gè)param是entry對(duì)象構(gòu)成的array,entry包含了觸發(fā)callback時(shí)DOM的位置信息

            //被監(jiān)聽(tīng)DOM元素的Rect信息
        boundingClientRect:  {
          bottom208
          height200
          left8
          right208
          top8
          width200
          x8
          y8
        }
        intersectionRatio1 //交叉比例
        // 被監(jiān)聽(tīng)元素與Root元素交叉部分矩形的Rect信息。
        intersectionRect: {
          bottom208,
          height200,
          left8,
          right208,
          top8,
          width200,
          x8,
          y8
        },
        // 是否處于交叉狀態(tài)
        isIntersectingtrue,
        isVisiblefalse,
        // Root元素的Rect信息
        rootBounds:  {
          bottom606,
          height606,
          left0,
          right476,
          top0,
          width476,
          x0,
          y0
        },
        // root元素
        target: div#target,
        time49.09999990463257

        常見(jiàn)場(chǎng)景

        乍一看IntersectionObserver好像沒(méi)啥用,單這個(gè)Api在某些場(chǎng)景下十分好用。

        比如我們有一個(gè)通過(guò)sticky固定在屏幕頂部的header元素,我們希望在觸發(fā)sticky時(shí)給header加一個(gè)shadow(很多table都有這樣的功能)

        一種很常見(jiàn)的做法是監(jiān)聽(tīng)scroll,當(dāng)滾動(dòng)一定距離時(shí)加上shadow即可。但是監(jiān)聽(tīng)scroll本身會(huì)早成一定的渲染壓力(scroll觸發(fā)非常頻繁),同時(shí)如果使用React這樣的框架又會(huì)造成額外的render,在用戶的視角看來(lái)更卡了。

        此時(shí)使用IntersectionObserver就很合適了,因?yàn)槲覀冃枰O(jiān)聽(tīng)的只是觸發(fā)sticky的一瞬間,其他的滾動(dòng)都是無(wú)效的,沒(méi)必要進(jìn)行計(jì)算。

            <!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 {
              margin0;
              padding0;
            }

            header {
              height80px;
              background-color#3498db;
              color: white;
              text-align: center;
              line-height80px;
              position: sticky;
              top0;
              z-index100;
            }

            .header-shadow {
              transition: box-shadow 0.3s ease;
            }

            .header-shadow.shadow {
              box-shadow0 2px 5px black;
            }

            section {
              height1000px;
              background-color#ecf0f1;
              padding20px;
            }
          
        </style>
        </head>
        <body>
          <div id="guard"></div>
          <header id="sticky-header" class="header-shadow">Sticky Header</header>

          <section>
            <p>向下滾動(dòng)觸發(fā)sticky時(shí)展示shadow</p>
          </section>

          <script>
            const header = document.getElementById('sticky-header');
            const section = document.querySelector('section');

            const options = {
              threshold1
            };
            //guard滾動(dòng)到可視區(qū)域以外時(shí)認(rèn)為觸發(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>
        ResizeObserver

        ResizeObserver是用于監(jiān)聽(tīng)DOM尺寸變化的observer,當(dāng)DOM尺寸變化是執(zhí)行callback

        基本使用

        和前面的api用法差不多,這里不過(guò)多介紹。

            const box = document.getElementById('box');

        const resizeObserver = new ResizeObserver(entries => {
          entries.forEach(entry => {
            console.log(entry)
          });
        });

        resizeObserver.observe(box);

        entry對(duì)象包含resize相關(guān)的信息,下面看一下entry的結(jié)構(gòu)

            {
          // 不同box-sizing下的尺寸
          borderBoxSize: [{
            blockSize200,
            inlineSize200,
          }],
          contentBoxSize: [{
            blockSize200,
            inlineSize200,
          }],
          contentRect: {
            bottom200,
            height200,
            left0,
            right200,
            top0,
            width200,
            x0,
            y0
          },
          // 在物理設(shè)備像素上的大小, 在不同的屏幕上尺寸不同例如Retina
          devicePixelContentBoxSize: [{
              blockSize300,
              inlineSize300
            }
          ],
          target: div#resizable-box
        }

        常見(jiàn)場(chǎng)景

        可以基于ResizeObserver實(shí)現(xiàn)一個(gè)簡(jiǎn)單的resize-detector(參考react-resize-detector),在尺寸變化時(shí)返回尺寸信息。

        實(shí)現(xiàn)一個(gè)簡(jiǎn)單的resize-detector

        這個(gè)demo做簡(jiǎn)單一點(diǎn),點(diǎn)擊盒子就算拖拽有效。

            <!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 {
              width200px;
              height200px;
              background-color#3498db;
              color: white;
              text-align: center;
              line-height200px;
              font-size24px;
              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>
        PerformanceObserver

        PerformanceObserver用于監(jiān)聽(tīng)瀏覽器的performance事件,方便在performance事件觸發(fā)時(shí)作統(tǒng)一處理。

        基本使用

            // mdn demo
        function perf_observer(list, observer{
          console.log(list)
        }
        var observer2 = new PerformanceObserver(perf_observer);
        // entryTypes用于指定要監(jiān)聽(tīng)的事件類(lèi)型
        observer2.observe({ entryTypes: ["measure"] });

        下面列一下常見(jiàn)的entryTypes

        • mark 用于標(biāo)記時(shí)間戳的事件
        • measure performance.measure觸發(fā)的事件
        • frame 網(wǎng)頁(yè)渲染的事件
        • navigation 導(dǎo)航的事件,例如頁(yè)面加載或重新加載
        • resource 資源加載事件
        • longtask 長(zhǎng)任務(wù)事件
        • paint:繪制事件,例如FP,F(xiàn)CP
        • layout-shift 用于監(jiān)視布局變化的事件

        對(duì)于對(duì)性能比較敏感的項(xiàng)目以及長(zhǎng)期性能監(jiān)控來(lái)說(shuō)這個(gè)api還是比較方便的。

        ReportingObserver

        ReportingObserver用于監(jiān)聽(tīng)瀏覽器報(bào)告的事件,例如廢棄API,過(guò)時(shí)特性,網(wǎng)絡(luò)錯(cuò)誤。做監(jiān)控SDK的同學(xué)應(yīng)該經(jīng)常能用到,日常業(yè)務(wù)代碼用的比較少。

        基本使用

        這里就簡(jiǎn)單看一下使用方法吧, 比較簡(jiǎn)單

                const observer = new ReportingObserver((reports, observer) => {
              reports.forEach(report => {
                console.log(report);
              });
            });

            // 監(jiān)聽(tīng)過(guò)時(shí)特性
            observer.observe({ types: ['deprecation'] });
        推薦閱讀  點(diǎn)擊標(biāo)題可跳轉(zhuǎn)

        1、JavaScript新數(shù)組方法介紹,不更改原數(shù)據(jù)的數(shù)

        2、js 如何判斷對(duì)象自身為空?很多人錯(cuò)了~

        3、JavaScript 中幾個(gè)優(yōu)雅的運(yùn)算符使用技巧

        瀏覽 158
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            插屁网 | 欧美特级AAAAAA | 骚逼做爱| 日本黄色爽片 | 逼逼喷水视频 | 日逼视屏 | 天天弄天天日 | 色女人在线视频 | 香港韩国日本三级 | 69国产精品久久久久久人 |