<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【W(wǎng)eb技術(shù)】1005- 關(guān)于 JS 與 CSS 是否阻塞 DOM 的渲染和解析

          共 12168字,需瀏覽 25分鐘

           ·

          2021-07-05 05:03


          最近系統(tǒng)梳理HTML5所有涉及到的標簽時,梳理至<link><script>標簽時,碰巧想到一個困擾很久的問題,即一般把<script>放在<body>尾部,<link>標簽放在<head>內(nèi)部,而頁面通過CDN引入第三方框架或庫時,基本都是將其<script>標簽放在<link>標簽前面。

          可能此方式已經(jīng)成為了約定俗成,但是究竟其好處在哪里,或者說其它的方式為什么不可取,想必你也和我有同樣的疑問,那就接著來往下看吧。

          準備工作

          首先需要做的準備工作是,搭建一個服務(wù)器,目的是為了返回css樣式和js腳本,并且讓服務(wù)器根據(jù)傳遞的參數(shù),固定延時返回數(shù)據(jù)。

          其目錄結(jié)構(gòu)如下,其中index.jsstyle.css就是用于返回的數(shù)據(jù),app.js為服務(wù)器啟動文件,index.html是用來測試案例的文件,剩余文件或文件夾可以忽略。

          ├── static
          │   ├── index.js
          │   ├── style.css
          ├── app.js
          ├── index.html
          ├── package.json
          ├── node_modules/
          復(fù)制代碼

          涉及的相關(guān)代碼也貼一下吧,方便復(fù)制調(diào)試。有必要說明一下,本地運行node app.js啟動后,瀏覽器輸入http://127.0.0.1:3000/就能訪問到index.html,而訪問style.css可以輸入http://127.0.0.1:3000/static/style.css?sleep=3000,其中sleep參數(shù)則可自由控制css文件延時返回,例如想要文件5s后返回就設(shè)置sleep=5000。

          // app.js
          const express = require('express')
          const fs = require('fs')
          const app = new express()
          const port = 3000

          const sleepFun = time => {
              return new Promise(res => {
                  setTimeout(() => {
                      res()
                  }, time)
              })
          }

          const filter = (req, res, next) => {
              const { sleep } = req.query || 0

              if (sleep) {
                  sleepFun(sleep).then(() => next())
              } else {
                  next()
              }
          }

          app.use(filter)

          app.use('/static/', express.static('./static/'))

          app.get('/'function (req, res, next{
              fs.readFile('./index.html''UTF-8', (err, data) => {
                  if (err) return
                  res.send(data)
              })
          })

          app.listen(port, () => {
              console.log(`app is running at http://127.0.0.1:${port}/`)
          })

          // static/index.js
          var p = document.querySelector('p');
          console.log(p);

          // static/index.css
          p { color: lightblue; }
          復(fù)制代碼

          接著就是index.html的準備工作,其中HTML部分的架子就長下面那樣,然后你只需要記住DOMContentLoaded事件將在頁面DOM解析完成后觸發(fā)。

          <!DOCTYPE html>
          <html lang="zh-CN">

          <head>
              <script>
                  document.addEventListener('DOMContentLoaded', () => {
                      var p = document.querySelector('p')
                      console.log(p)
                  })
              </script>

          </head>

          <body>
              <p>hello world</p>
          </body>

          </html>

          復(fù)制代碼

          CSS 不會阻塞 DOM 解析,但是會阻塞 DOM 渲染

          首先在index.html插入如下<link>標簽,然后在瀏覽器輸入http://127.0.0.1:3000/訪問此頁面。

          <head>
              <script>
                  document.addEventListener('DOMContentLoaded', () => {
                      var p = document.querySelector('p')
                      console.log(p)
                  })
              </script>

              <link rel="stylesheet" href="./static/style.css?sleep=3000">
          </head>


          <body>
              <p>hello world</p>
          </body>

          復(fù)制代碼

          頁面初始顯示為空白,控制臺打印出了p元素,同時瀏覽器標簽頁上加載loading,3s后頁面顯示出淺藍色的hello world

          在這里插入圖片描述

          以上情況也就說明,CSS不會阻塞DOM的解析,如果說CSS阻塞DOM解析的話,那么p標簽不會被解析,進而DOM不會被解析完成,CSS請求過程中也不可能會觸發(fā)DOMContentLoaded事件。而且在css請求過程中,控制臺立即打印出了p元素,由此也驗證了此結(jié)論的正確性。

          另一個情況就是,雖然DOM很早就被解析完成,但是p標簽卻遲遲沒有渲染,原因在于CSS樣式還未請求完成,在樣式獲取后hello world才被渲染出來,所以說CSS會阻塞頁面渲染。

          簡單闡述一下瀏覽器的解析渲染過程,解析DOM生成DOM Tree,解析CSS生成CSSOM Tree,兩者結(jié)合生成render tree渲染樹,最后瀏覽器根據(jù)渲染樹渲染至頁面。由此可以看出DOM Tree的解析和CSSOM Tree的解析是互不影響的,兩者是并行的。因此CSS不會阻塞頁面DOM的解析,但是由于render tree的生成是依賴DOM TreeCSSOM Tree的,因此CSS必然會阻塞DOM的渲染。

          更為嚴謹一點的說,CSS會阻塞render tree的生成,進而會阻塞DOM的渲染。

          JS 會阻塞 DOM 解析

          為了避免加載CSS造成的干擾,如下僅關(guān)注JS的執(zhí)行情況,其中for循環(huán)的循環(huán)體中邏輯暫不考慮,僅僅是讓JS執(zhí)行更多時間。

          <head>
              <script>
                  document.addEventListener('DOMContentLoaded', () => {
                      var p = document.querySelector('p')
                      console.log(p)
                  })
              </script>

          </head>

          <body>
              <script>
                  const p = document.querySelector('p')
                  console.log(p)
              
                  for (var i = 0, arr = []; i < 100000000; i++) {
                      arr.push(i)
                  }
              </
          script>
              <p>hello world</p>
          </body>
          復(fù)制代碼

          瀏覽器訪問頁面,初始時為空白且控制臺打印null,瀏覽器loading短暫延時后,控制臺打印出p標簽同時頁面渲染出hello world。

          在這里插入圖片描述

          以上情況很容易說明JS會阻塞DOM解析了,JS執(zhí)行初控制臺打印null,因為此時p標簽還未被解析,for循環(huán)執(zhí)行時,可以明顯感覺到執(zhí)行耗時,執(zhí)行完成p標簽被解析,此時觸發(fā)DOMContentLoaded事件,控制臺打印出p標簽,同時頁面渲染出hello world。

          比較合理的解釋就是,首先瀏覽器無法知曉JS的具體內(nèi)容,倘若先解析DOM,萬一JS內(nèi)部全部刪除掉DOM,那么瀏覽器就白忙活了,所以就干脆暫停解析DOM,等到JS執(zhí)行完成再繼續(xù)解析。

          CSS 會阻塞 JS 的執(zhí)行

          如下在頁內(nèi)JS腳本前插入<link>標簽,并且延時3s獲取CSS樣式。

          <head>
              <script>
                  document.addEventListener('DOMContentLoaded', () => {
                      var p = document.querySelector('p')
                      console.log(p)
                  })
              </script>

              <link rel="stylesheet" href="./static/style.css?sleep=3000">
              <script src="./static/index.js"></script>
          </head>


          <body>
              <p>hello world</p>
          </body>

          復(fù)制代碼

          初始頁面空白,瀏覽器loading加載3s后,控制臺打印出null,緊接著打印出p標簽,同時頁面渲染出淺藍色p標簽。

          在這里插入圖片描述

          此情況好像是CSS不僅阻塞了DOM的解析,而且也阻塞了DOM渲染。

          但是首先要思考下是什么阻塞了DOM的解析,剛剛已經(jīng)證明了CSS不會阻塞DOM的解析,所以只可能是JS阻塞了DOM解析。但是JS只有兩行代碼,不會阻塞長達3s左右的時間。所以只有一個可能就是CSS會阻塞JS的執(zhí)行。

          因此輸出結(jié)果也能大致分析出來了,首先解析到第一個<script>標簽,document綁定上DOMContentLoaded事件,緊接著解析到link標簽,瀏覽器請求CSS樣式,由于CSS不會阻塞DOM解析,因此瀏覽器繼續(xù)向下解析,發(fā)現(xiàn)第二個<script>標簽,瀏覽器請求JS腳本,此時JS獲取完成,但是由于CSS還在獲取,所以不能立即執(zhí)行。

          而第二個<script>不能立即執(zhí)行,導(dǎo)致它后面的p標簽也沒辦法解析,原因則是JS會阻塞DOM解析。只有等待到CSS樣式獲取成功后,此時JS立即執(zhí)行,控制臺輸出null,然后瀏覽器繼續(xù)解析到p標簽,解析完成,DOMContentLoaded事件觸發(fā),控制臺輸出p標簽,最后淺藍色hello world渲染至頁面。

          其實這樣做也是有道理的,設(shè)想JS腳本中的內(nèi)容是獲取DOM元素的CSS樣式屬性,如果JS想要獲取到DOM最新的正確的樣式,勢必需要所有的CSS加載完成,否則獲取的樣式可能是錯誤或者不是最新的。因此要等到JS腳本前面的CSS加載完成,JS才能再執(zhí)行,并且不管JS腳本中是否獲取DOM元素的樣式,瀏覽器都要這樣做。

          回溯文章開頭的那個疑問,所以一般將<script>放在<link>標簽前面是有道理的。

          JS 會觸發(fā)頁面渲染

          如下CSS采用頁內(nèi)方式,其中顏色名及其rgb值分別為淺綠色lightbluergb(144, 238, 144))、粉色pinkrgb(255, 192, 203))。

          // index.html
          <head>
              <style>
                  p {
                      color: lightgreen;
                  }
              </style>

          </head>

          <body>
              <p>hello</
          p>
              <script src="./static/index.js?sleep=2000"></script>
              <p>beautiful</p>
              <style>
                  p {
                      color: pink;
                  }
              </style>

              <script src="./static/index.js?sleep=4000"></script>
              <p>world</p>
              <style>
                  p {
                      color: lightblue;
                  }
              </style>

          </body>

          // static/i
          ndex.js
          var p = document.querySelector('p');
          var style = window.getComputedStyle(p, null);
          console.log(style.color);
          復(fù)制代碼

          頁面初始渲染出淺綠色hello,緊接著2s后渲染出粉色hello beautiful且控制臺打印rgb(144, 238, 144),然后又2s后渲染出淺藍色hello beautiful world且控制臺打印rgb(255, 192, 203)

          在這里插入圖片描述

          上述結(jié)果大致分析為瀏覽器首先解析第一個<style>標簽和hello文本的p標簽,此時繼續(xù)向下解析發(fā)現(xiàn)了第一個<script>標簽,緊接著觸發(fā)一次渲染,由于此過程非常快所以頁面初始就能看到淺綠色hello。

          然后瀏覽器發(fā)出JS請求,2sJS獲取完成立即運行控制臺輸出rgb(144, 238, 144)JS運行完成后瀏覽器繼續(xù)向下解析到beautiful文本的p標簽和第二個<style>標簽,再繼續(xù)向下解析發(fā)現(xiàn)了第二個<script>標簽,觸發(fā)一次渲染,這個過程也是非常快,所以可以看到控制臺輸出結(jié)果和渲染粉色hello beautiful幾乎是同時的。

          解析到第二個<script>標簽時,瀏覽器不會發(fā)出請求(稍作解釋),2s后獲取到JS腳本并執(zhí)行,控制臺輸出rgb(255, 192, 203),緊接著瀏覽器繼續(xù)向下解析到world文本的p標簽和第三個<style>標簽,此時DOM解析完成,再進行正常的渲染,這個過程也是非??欤砸材芸吹娇刂婆_輸出結(jié)果和渲染淺藍色hello beautiful world幾乎是同時的。

          現(xiàn)在來解答剛才那個問題,瀏覽器解析DOM時,雖然會一行一行向下解析,但是它會預(yù)先加載具有引用標記的外部資源(例如帶有src標記的<script>標簽),而在解析到此標簽時,則無需再去加載,直接運行,以此提高運行效率。所以就會有上述兩個輸出結(jié)果間隔2s的情況,而不是4s,因為瀏覽器預(yù)先就一起加載了兩個<script>腳本,第一個<script>腳本加載完成時,第二個<script>腳本還剩大概2s加載完成。

          而這個結(jié)論才是解釋為何CSS會阻塞JS的執(zhí)行的真正原因,瀏覽器無法預(yù)先知道腳本的具體內(nèi)容,因此在碰到<script>標簽時,只好先渲染一次頁面,確保<script>腳本內(nèi)能獲取到DOM的最新的樣式。倘若在決定渲染頁面時,還有尚未加載完成的CSS樣式,只能等待其加載完成再去渲染頁面。

          Body 內(nèi)的 CSS

          來看一個較為特殊的情況。

          <head>
              <script>
                  document.addEventListener('DOMContentLoaded', () => {
                      var p = document.querySelector('p')
                      console.log(p)
                  })
              </script>

          </head>

          <body>
              <p>hello</
          p>
              <link rel="stylesheet" href="./static/style.css?sleep=3000">
              <p>world</p>
          </body>

          復(fù)制代碼

          按照上述的所有結(jié)論,預(yù)先分析一下運行結(jié)果,首先瀏覽器解析<script>腳本,document上綁定了DOMContentLoaded事件,緊接著瀏覽器繼續(xù)向下解析,發(fā)現(xiàn)了文本為hellop標簽和<link>標簽,瀏覽器發(fā)起CSS請求,由于CSS不會阻塞DOM解析,瀏覽器繼續(xù)向下解析至文本為worldp標簽,此時頁面解析完成,DOMContentLoaded事件觸發(fā)控制臺輸出p標簽,3s后頁面渲染出淺藍色hello world。

          因此按照分析,初始時頁面空白,瀏覽器loading加載3s后,控制臺打印出p標簽,同時頁面渲染出淺藍色hello world

          但是實際結(jié)果并不是這樣,而是頁面初始就渲染出hello,3s后頁面渲染出淺藍色hello world并且打印p標簽。

          在這里插入圖片描述

          如下是我個人的分析和理解,首先是瀏覽器解析并運行<script>標簽,然后在解析文本為hellop標簽,當解析到<link>標簽時,觸發(fā)一次渲染,然后瀏覽器發(fā)起CSS請求,但是此時瀏覽器不會繼續(xù)向下解析,而是將<link>標簽當做是DOM的一部分,換句話說瀏覽器將其認為是特殊的DOM元素,這個DOM元素的特殊性就在于需要進行加載,因此瀏覽器不會繼續(xù)向下解析,所以也就沒有DOMContentLoaded的輸出結(jié)果。

          3s<link>這個特殊的DOM元素解析完成,瀏覽器繼續(xù)向下解析world文本的p標簽,此時觸發(fā)DOMContentLoaded事件,再進行正常的渲染,頁面渲染出淺藍色hello world,由于此過程非???,所以控制臺輸出和渲染淺藍色hello world幾乎是同時的。

          上述僅僅是我個人的分析和猜測,可以不必理會,僅作為討論,所以也不敢妄下結(jié)論,誤人子弟,此小節(jié)僅走馬觀花即可。

          綜上所述

          綜合上述所有情況,可以得出如下結(jié)論。

          • CSS不會阻塞DOM解析,但是會阻塞DOM渲染,嚴謹一點則是CSS會阻塞render tree的生成,進而會阻塞DOM的渲染
          • JS會阻塞DOM解析
          • CSS會阻塞JS的執(zhí)行
          • 瀏覽器遇到<script>標簽且沒有deferasync屬性時會觸發(fā)頁面渲染
          • Body內(nèi)部的外鏈CSS較為特殊,請慎用


          關(guān)于本文
          來源:Don_GW
          https://juejin.cn/post/6973949865130885157

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計模式 重溫系列(9篇全)
          4. 正則 / 框架 / 算法等 重溫系列(16篇全)
          5. Webpack4 入門(上)|| Webpack4 入門(下)
          6. MobX 入門(上) ||  MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點擊“閱讀原文”查看 120+ 篇原創(chuàng)文章

          瀏覽 72
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美a网站 | 家庭乱伦毛片 | 无码一区二区三区四区五区六区七区 | 国产精品久久久久久久久久久久久 | 日一日射一射无码视频 |