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>

        常用的 DOM 優(yōu)化

        共 6252字,需瀏覽 13分鐘

         ·

        2021-08-25 14:46

        隨著用戶體驗(yàn)的日益重視,前端性能對用戶體驗(yàn)的影響備受關(guān)注,但由于引起性能問題的原因相對復(fù)雜,我們很難從某一方面或某幾個(gè)方面來全面解決它,接下來用一系列文章來深層次探討與梳理有關(guān) Javascript 性能的方方面面,以填補(bǔ)并夯實(shí)大家的知識結(jié)構(gòu)。


        接下來我們來聊一聊關(guān)于 DOM 操作相關(guān)的性能優(yōu)化。前端工程師,一直說的一句話:操作 DOM 的成本很高,不要輕易去操作 DOM。尤其是 React、vue 等 MV*框架的出現(xiàn),數(shù)據(jù)驅(qū)動(dòng)視圖的模式越發(fā)深入人心,jQuery 時(shí)代提供的強(qiáng)大便利地操作 DOM 的 API 在前端工程里用的越來越少。刨根問底,這里說的成本,到底高在哪兒呢?


        DOM 操作成本到底高在哪兒?


        什么是 DOM?可能很多人第一反應(yīng)就是 div、p、span 等 html 標(biāo)簽(至少我是),但要知道,DOM 是 Model,是 Object Model,對象模型,是為 HTML(and XML)提供的 API。HTML(Hyper Text Markup Language)是一種標(biāo)記語言,HTML 在 DOM 的模型標(biāo)準(zhǔn)中被視為對象,DOM 只提供編程接口,卻無法實(shí)際操作 HTML 里面的內(nèi)容。但在瀏覽器端,前端們可以用腳本語言(JavaScript)通過 DOM 去操作 HTML 內(nèi)容。


        實(shí)質(zhì)上還存在 CSSOM:CSS Object Model,瀏覽器將 CSS 代碼解析成樹形的數(shù)據(jù)結(jié)構(gòu),與DOM 是兩個(gè)獨(dú)立的數(shù)據(jù)結(jié)構(gòu)。


        接下來說一說瀏覽器渲染。過程。


        討論 DOM 操作成本,肯定要先了解該成本的來源,那么就離不開瀏覽器渲染。


        1. 解析 HTML,構(gòu)建 DOM 樹(這里遇到外鏈,此時(shí)會(huì)發(fā)起請求)

        2. 解析 CSS,生成 CSS 規(guī)則樹

        1. 合并 DOM 樹和 CSS 規(guī)則,生成 render 樹

        2. 布局 render 樹(Layout/reflow),負(fù)責(zé)各元素尺寸、位置的計(jì)算

        1. 繪制 render 樹(paint),繪制頁面像素信息

        2. 瀏覽器會(huì)將各層的信息發(fā)送給 GPU,GPU 將各層合成(composite),顯示在屏幕上


        1.構(gòu)建DOM 樹

        <html> <head>   <meta name="viewport" content="width=device-width,initial-scale=1">   <link href="style.css" rel="stylesheet">   <title>Critical Path</title> </head> <body>   <p>Hello     <span>web performance</span>    students!   </p>   <div>    <img src="awesome-photo.jpg">  </div> </body></html>

        無論是 DOM 還是 CSSOM,都是要經(jīng)過 Bytes → characters → tokens → nodes →object model 這個(gè)過程。


        DOM 樹構(gòu)建過程:當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn)都構(gòu)建好后才會(huì)去構(gòu)建當(dāng)前節(jié)點(diǎn)的下一個(gè)兄弟節(jié)點(diǎn)。屬于深度優(yōu)先遍歷過程。


        2.構(gòu)建CSSOM 樹

        上述也提到了 CSSOM 的構(gòu)建過程,也是樹的結(jié)構(gòu),在最終計(jì)算各個(gè)節(jié)點(diǎn)的樣式時(shí),瀏覽器都會(huì)先從該節(jié)點(diǎn)的普遍屬性(比如 body 里設(shè)置的全局樣式)開始,再去應(yīng)用該節(jié)點(diǎn)的具體屬性。還有要注意的是,每個(gè)瀏覽器都有自己默認(rèn)的樣式表,因此很多時(shí)候這棵 CSSOM 樹只是對這張默認(rèn)樣式表的部分替換。


        3.生成render 樹

        簡單描述這個(gè)過程:

        DOM 樹從根節(jié)點(diǎn)開始遍歷可見節(jié)點(diǎn),這里之所以強(qiáng)調(diào)了“可見”,是因?yàn)槿绻龅皆O(shè)置了類似 display: none;的不可見節(jié)點(diǎn),在 render 過程中是會(huì)被跳過的(但 visibility: hidden; opacity: 0 這種仍舊占據(jù)空間的節(jié)點(diǎn)不會(huì)被跳過 render),保存各個(gè)節(jié)點(diǎn)的樣式信息及其余節(jié)點(diǎn)的從屬關(guān)系。


        4. Layout 布局
        有了各個(gè)節(jié)點(diǎn)的樣式信息和屬性,但不知道各個(gè)節(jié)點(diǎn)的確切位置和大小,所以要通過布局將樣式信息和屬性轉(zhuǎn)換為實(shí)際可視窗口的相對大小和位置。


        5.Paint 繪制
        萬事俱備,最后只要將確定好位置大小的各節(jié)點(diǎn),通過 GPU 渲染到屏幕的實(shí)際像素。

        Tips

        • 在上述渲染過程中,前 3 點(diǎn)可能要多次執(zhí)行,比如 js 腳本去操作 dom、更改 css 樣式時(shí),瀏覽器又要重新構(gòu)建 DOM、CSSOM 樹,重新 render,重新 layout、paint;

        • Layout 在 Paint 之前,因此每次 Layout 重新布局(reflow 回流)后都要重新出發(fā) Paint 渲染,這時(shí)又要去消耗 GPU;

        • Paint 不一定會(huì)觸發(fā) Layout,比如改個(gè)顏色改個(gè)背景;(repaint 重繪)

        • 圖片下載完也會(huì)重新觸發(fā) Layout 和 Paint;


        何時(shí)觸發(fā) reflow 和 repaint
        reflow(回流):根據(jù) Render Tree 布局(幾何屬性),意味著元素的內(nèi)容、結(jié)構(gòu)、位置或尺寸發(fā)生了變化,需要重新計(jì)算樣式和渲染樹;
        repaint(重繪): 意味著元素發(fā)生的改變只影響了節(jié)點(diǎn)的一些樣式(背景色,邊框顏色, 文字顏色等),只需要應(yīng)用新樣式繪制這個(gè)元素就可以了;
        reflow 回流的成本開銷要高于 repaint 重繪,一個(gè)節(jié)點(diǎn)的回流往往會(huì)導(dǎo)致子節(jié)點(diǎn)以及同級節(jié)點(diǎn)的回流;


        引起 reflow 回流
        現(xiàn)代瀏覽器會(huì)對回流做優(yōu)化,它會(huì)等到足夠數(shù)量的變化發(fā)生,再做一次批處理回流。


        1. 頁面第一次渲染(初始化)

        2. DOM 樹變化(如:增刪節(jié)點(diǎn))

        1. Render 樹變化(如:padding 改變)

        2. 瀏覽器窗口 resize

        1. 獲取元素的某些屬性:瀏覽器為了獲得正確的值也會(huì)提前觸發(fā)回流,這樣就使得瀏覽器的優(yōu)化失效了,這些屬性包括 offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、調(diào)用了 getComputedStyle()或者 IE 的currentStyle


        引起 repaint 重繪

        1. reflow 回流必定引起 repaint 重繪,重繪可以單獨(dú)觸發(fā)

        2. 背景色、顏色、字體改變(注意:字體大小發(fā)生變化時(shí),會(huì)觸發(fā)回流)


        優(yōu)化 reflow、repaint 觸發(fā)次數(shù)

        • 避免逐個(gè)修改節(jié)點(diǎn)樣式,盡量一次性修改

        • 使用 DocumentFragment 將需要多次修改的 DOM 元素緩存,最后一次性 append 到真實(shí) DOM 中渲染

        • 可以將需要多次修改的 DOM 元素設(shè)置 display: none,操作完再顯示。(因?yàn)殡[藏元素不在 render 樹內(nèi),因此修改隱藏元素不會(huì)觸發(fā)回流重繪)

        • 避免多次讀取某些屬性(見上)

        • 將復(fù)雜的節(jié)點(diǎn)元素脫離文檔流,降低回流成本


        操作 DOM 具體的成本,說到底是造成瀏覽器回流 reflow 和重繪 reflow,從而消耗 GPU 資源。


        既然 DOM 操作是很耗性能的,我們該怎么做盡量的減少性能的損耗呢?


        DOM 優(yōu)化常用方法

        優(yōu)化節(jié)點(diǎn)修改
        使用 cloneNode 在外部更新節(jié)點(diǎn)然后再通過 replace 與原始節(jié)點(diǎn)互換。

        var orig = document.getElementById('container');var clone = orig.cloneNode(true);var list = ['foo', 'bar', 'baz'];var content;for (var i = 0; i < list.length; i++) { content = document.createTextNode(list[i]); clone.appendChild(content);}orig.parentNode.replaceChild(clone, orig)

        優(yōu)化節(jié)點(diǎn)添加

        多個(gè)節(jié)點(diǎn)插入操作,即使在外面設(shè)置節(jié)點(diǎn)的元素和風(fēng)格再插入,由于多個(gè)節(jié)點(diǎn)還是會(huì)引發(fā)多次 reflow。

        優(yōu)化的方法是創(chuàng)建 DocumentFragment,在其中插入節(jié)點(diǎn)后再添加到頁面。
        如 JQuery 中所有的添加節(jié)點(diǎn)的操作如 append,都是最終調(diào)用DocumentFragment 來實(shí)現(xiàn)的。


        createSafeFragment(document) {   var list = nodeNames.split( "|" ),   safeFrag = document.createDocumentFragment();     if (safeFrag.createElement) {       while (list.length) {         safeFrag.createElement(           list.pop();         );       };     }; return safeFrag;};


        優(yōu)化 CSS 樣式轉(zhuǎn)換。

        如果需要?jiǎng)討B(tài)更改 CSS 樣式,盡量采用觸發(fā) reflow 次數(shù)較少的方式。


        如以下代碼逐條更改元素的幾何屬性,理論上會(huì)觸發(fā)多次 reflow。

        element.style.fontWeight = 'bold' ;element.style.marginLeft= '30px' ; element.style.marginRight = '30px' ;

        可以通過直接設(shè)置元素的 className 直接設(shè)置,只會(huì)觸發(fā)一次 reflow。

        element.className = 'selectedAnchor' ;

        減少 DOM 元素?cái)?shù)量
        在 console 中執(zhí)行命令查看 DOM 元素?cái)?shù)量。
        document.getElementsByTagName( '*' ).length
        正常頁面的 DOM 元素?cái)?shù)量一般不應(yīng)該超過 1000。
        DOM 元素過多會(huì)使 DOM 元素查詢效率,樣式表匹配效率降低,是頁面性能最主要的瓶頸之一。

        DOM 操作優(yōu)化
        DOM 操作性能問題主要有以下原因。
        DOM 元素過多導(dǎo)致元素定位緩慢。
        大量的 DOM 接口調(diào)用。
        JAVASCRIPT 和 DOM 之間的交互需要通過函數(shù) API 接口來完成,造成延時(shí),尤其是在循環(huán)語句中。

        DOM 操作觸發(fā)頻繁的 reflow(layout)和 repaint。

        layout 發(fā)生在 repaint 之前,所以 layout 相對來說會(huì)造成更多性能損耗。
        reflow(layout)就是計(jì)算頁面元素的幾何信息。
        repaint 就是繪制頁面元素。
        對 DOM 進(jìn)行操作會(huì)導(dǎo)致瀏覽器執(zhí)行回流 reflow。


        解決方案。

        純 JAVASCRIPT 執(zhí)行時(shí)間是很短的。

        最小化 DOM 訪問次數(shù),盡可能在 js 端執(zhí)行。

        如果需要多次訪問某個(gè) DOM 節(jié)點(diǎn),請使用局部變量存儲(chǔ)對它的引用。

        謹(jǐn)慎處理 HTML 集合(HTML 集合實(shí)時(shí)聯(lián)系底層文檔),把集合的長度緩存到一個(gè)變量中,并在迭代中使用它,如果需要經(jīng)常操作集合,建議把它拷貝到一個(gè)數(shù)組中。

        如果可能的話,使用速度更快的 API,比如 querySelectorAll 和firstElementChild。

        要留意重繪和重排。

        批量修改樣式時(shí),離線操作 DOM 樹。
        使用緩存,并減少訪問布局的次數(shù)。
        動(dòng)畫中使用絕對定位,使用拖放代理。

        使用事件委托來減少事件處理器的數(shù)量。


        優(yōu)化 DOM 交互 >在 JAVASCRIPT 中,DOM 操作和交互要消耗大量時(shí)間,因?yàn)樗鼈兺枰匦落秩菊麄€(gè)頁面或者某一個(gè)部分。
        最小化現(xiàn)場更新。

        當(dāng)需要訪問的 DOM 部分已經(jīng)已經(jīng)被渲染為頁面中的一部分,那么 DOM操作和交互的過程就是再進(jìn)行一次現(xiàn)場更新。

        現(xiàn)場更新是需要針對現(xiàn)場(相關(guān)顯示頁面的部分結(jié)構(gòu))立即進(jìn)行更新,每一個(gè)更改(不管是插入單個(gè)字符還是移除整個(gè)片段),都有一個(gè)性能損耗。

        現(xiàn)場更新進(jìn)行的越多,代碼完成執(zhí)行所花的時(shí)間也越長。


        多使用 innerHTML。

        有兩種在頁面上創(chuàng)建 DOM 節(jié)點(diǎn)的方法:

        使用諸如 createElement()和 appendChild()之類的 DOM 方法。

        使用 innerHTML。

        當(dāng)使用 innerHTML 設(shè)置為某個(gè)值時(shí),后臺(tái)會(huì)創(chuàng)建一個(gè)HTML 解釋器,然后使用內(nèi)部的 DOM 調(diào)用來創(chuàng)建 DOM 結(jié)構(gòu),而非基于 JAVASCRIPT 的 DOM 調(diào)用。由于內(nèi)部方法是編譯好的而非解釋執(zhí)行,故執(zhí)行的更快。對于小的DOM 更改,兩者效率差不多,但對于大的 DOM 更改, innerHTML 要比標(biāo)準(zhǔn)的 DOM 方法創(chuàng)建同樣的 DOM 結(jié)構(gòu)快得多。


        回流 reflow。
        發(fā)生場景。

        改變窗體大小。
        更改字體。
        添加移除 stylesheet 塊。
        內(nèi)容改變哪怕是輸入框輸入文字。

        CSS 虛類被觸發(fā)如 :hover。
        更改元素的 className。
        當(dāng)對 DOM 節(jié)點(diǎn)執(zhí)行新增或者刪除操作或內(nèi)容更改時(shí)。
        動(dòng)態(tài)設(shè)置一個(gè) style 樣式時(shí)(比如element.style.width="10px")。

        當(dāng)獲取一個(gè)必須經(jīng)過計(jì)算的尺寸值時(shí),比如訪問 offsetWidth、clientHeight 或者其他需要經(jīng)過計(jì)算的 CSS 值。


        解決問題的關(guān)鍵,就是限制通過 DOM 操作所引發(fā)回流的次數(shù)。
        在對當(dāng)前 DOM 進(jìn)行操作之前,盡可能多的做一些準(zhǔn)備工作,保證 N 次創(chuàng)建,1 次寫入。
        在對 DOM 操作之前,把要操作的元素,先從當(dāng)前 DOM 結(jié)構(gòu)中刪除:
        通過 removeChild()或者 replaceChild()實(shí)現(xiàn)真正意義上的刪除。

        設(shè)置該元素的 display 樣式為“none”。

        每次修改元素的 style 屬性都會(huì)觸發(fā)回流操作。

        element.style.backgroundColor = "blue";
        使用更改 className 的方式替換 style.xxx=xxx 的方式。
        使用 style.cssText = '';一次寫入樣式。
        避免設(shè)置過多的行內(nèi)樣式。
        添加的結(jié)構(gòu)外元素盡量設(shè)置它們的位置為 fixed 或absolute。

        避免使用表格來布局。

        避免在 CSS 中使用 JavaScript expressions(IE only)。
        將獲取的 DOM 數(shù)據(jù)緩存起來。這種方法,對獲取那些會(huì)觸發(fā)回流操作的屬性(比如 offsetWidth 等)尤為重要。
        當(dāng)對 HTMLCollection 對象進(jìn)行操作時(shí),應(yīng)該將訪問的次數(shù)盡可能的降至最低,最簡單的,你可以將 length 屬性緩存在一個(gè)本地變量中,這樣就能大幅度的提高循環(huán)的效率。


        最后聽一首悅耳的歌放松放松,回憶學(xué)到的東西。

        點(diǎn)擊下面f4f92c276afc27a8de2c4dc30eae456a.webp播放音樂

        長按二維碼關(guān)注,一起努力。

        助力尋人啟事

        微信公眾號回復(fù)?加群?一起學(xué)習(xí)。

        瀏覽 91
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(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>
            A级毛片在线观看 | 涩涩小视频在线观看 | 小黄片在线 | 成人午夜AA | 亚洲视频在线观看视频 | 青青射| 宋佳双乳高耸呻吟不止 | 干屄免费视频 | av中文无码 | 91狼友社 |