瀏覽器環境概述

瀏覽器環境概述

1. 代碼嵌入網頁的方法

網頁中嵌入 JavaScript 代碼,主要有四種方法。

  1. <script>元素直接嵌入代碼。
<script id="mydata" type="x-custom-data">
  console.log('Hello World');
</script>
<!-- 可以使用<script>節點的text屬性讀出它的內容,<script>存在於DOM中 -->
document.getElementById('mydata').text
  1. <script>標籤加載外部腳本
<!-- script標籤允許設置一個integrity屬性,寫入該外部腳本的 Hash 簽名,用來驗證腳本的一致性。-->
<script src="/assets/application.js"
  integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs=">
</script>
  1. 事件屬性,(比如onclick和onmouseover)
<button id="myBtn" onclick="console.log(this.id)">點擊</button>
  1. URL 協議
<a href="javascript: void new Date().toLocaleTimeString();">點擊</a>
<a href="javascript: new Date().toLocaleTimeString();void 0;">點擊</a>
<!-- 在腳本前加上void,或者在腳本最後加上void 0,可以防止書籤替換掉當前文檔。 -->

2. script 元素

2.1 工作原理

正常的網頁加載流程

  1. 瀏覽器一邊下載 HTML 網頁,一邊開始解析。也就是說,不等到下載完,就開始解析。
  2. 解析過程中,瀏覽器發現<script>元素,就暫停解析,把網頁渲染的控制權轉交給 JavaScript 引擎。
  3. 如果<script>元素引用了外部腳本,就下載該腳本再執行,否則就直接執行代碼 。
  4. JavaScript 引擎執行完畢,控制權交還渲染引擎,恢復往下解析 HTML 網頁。

    加載外部腳本時,瀏覽器會暫停頁面渲染,等待腳本下載並執行完成後,再繼續渲染。原因是 JavaScript 代碼可以修改 DOM,所以必須把控制權讓給它,否則會導致複雜的線程競賽的問題

    如果外部腳本加載時間很長(一直無法完成下載),那麼瀏覽器就會一直等待腳本下載完成,造成網頁長時間失去響應,瀏覽器就會呈現“假死”狀態,這被稱爲阻塞效應。同樣解析和執行 CSS,也會產生阻塞。
    爲了避免這種情況,較好的做法是將<script>標籤都放在頁面底部,而不是頭部,如下:

<body>
  <!-- 其他代碼  -->
  <script>
    console.log(document.body.innerHTML);
  </script>
</body>

    此外,對於來自同一個域名的資源,比如腳本文件、樣式表文件、圖片文件等,瀏覽器一般有限制,同時最多下載6~20個資源,即最多同時打開的 TCP 連接有限制,這是爲了防止對服務器造成太大壓力。如果是來自不同域名的資源,就沒有這個限制。所以,通常把靜態文件放在不同的域名之下,以加快下載速度。

2.2 defer 屬性

    爲了解決腳本文件下載阻塞網頁渲染的問題,一個方法是對<script>元素加入defer屬性。它的作用是延遲腳本的執行,等到 DOM 加載生成後,再執行腳本。且執行順序就是它們在頁面上出現的順序。

<script src="a.js" defer></script>
<script src="b.js" defer></script>

    對於內置而不是加載外部腳本的script標籤,以及動態生成的script標籤,defer屬性不起作用。另外,使用defer加載的外部腳本不應該使用document.write方法。

2.3 async 屬性

    解決“阻塞效應”的另一個方法是對<script>元素加入async屬性。會在解析 HTML 網頁同時並行下載<script>標籤中的外部腳本,腳本下載完成,瀏覽器暫停解析 HTML 網頁,開始執行下載的腳本。腳本執行完畢,瀏覽器恢復解析 HTML 網頁。

<script src="a.js" async></script>
<script src="b.js" async></script>

    async屬性可以保證腳本下載的同時,瀏覽器繼續渲染。需要注意的是,一旦採用這個屬性,就無法保證腳本的執行順序。哪個腳本先下載結束,就先執行那個腳本。另外,使用async屬性的腳本文件裏面的代碼,不應該使用document.write方法。

defer屬性和async屬性到底應該使用哪一個?

    一般來說,如果腳本之間沒有依賴關係,就使用async屬性,如果腳本之間有依賴關係,就使用defer屬性。如果同時使用async和defer屬性,後者不起作用,瀏覽器行爲由async屬性決定。

3. 瀏覽器的組成

瀏覽器的核心是兩部分:渲染引擎和 JavaScript 解釋器(又稱 JavaScript 引擎)。

3.1 渲染引擎

渲染引擎的主要作用是,將網頁代碼渲染爲用戶視覺可以感知的平面文檔

不同的瀏覽器有不同的渲染引擎。

  • Firefox:Gecko 引擎
  • Safari:WebKit 引擎
  • Chrome:Blink 引擎
  • IE: Trident 引擎
  • Edge: EdgeHTML 引擎

渲染引擎處理網頁,通常分成四個階段。

  1. 解析代碼:HTML 代碼解析爲 DOM,CSS 代碼解析爲 CSSOM(CSS Object Model)。
  2. 對象合成:將 DOM 和 CSSOM 合成一棵渲染樹(render tree)。
  3. 佈局:計算出渲染樹的佈局(layout)。
  4. 繪製:將渲染樹繪製到屏幕。

    以上四步並非嚴格按順序執行,往往第一步還沒完成,第二步和第三步就已經開始了。所以,會看到這種情況:網頁的 HTML 代碼還沒下載完,但瀏覽器已經顯示出內容了。

3.2 重流和重繪

    渲染樹轉換爲網頁佈局,稱爲“佈局流”(flow);佈局顯示到頁面的這個過程,稱爲“繪製”(paint)。它們都具有阻塞效應,並且會耗費很多時間和計算資源。
    頁面生成以後,腳本操作和樣式表操作,都會觸發“重流”(reflow)和“重繪”(repaint)。用戶的互動也會觸發重流和重繪,比如設置了鼠標懸停(a:hover)效果、頁面滾動、在輸入框中輸入文本、改變窗口大小等等。
    重流和重繪並不一定一起發生,重流必然導致重繪,重繪不一定需要重流。比如改變元素顏色,只會導致重繪,而不會導致重流;改變元素的佈局,則會導致重繪和重流。
大多數情況下,瀏覽器會智能判斷,將重流和重繪只限制到相關的子樹上面,最小化所耗費的代價,而不會全局重新生成網頁。
    作爲開發者,應該儘量設法降低重繪的次數和成本。比如,儘量不要變動高層的 DOM 元素,而以底層 DOM 元素的變動代替;再比如,重繪table佈局和flex佈局,開銷都會比較大。

下面是一些優化技巧。

  • 讀取 DOM 或者寫入 DOM,儘量寫在一起,不要混雜。不要讀取一個 DOM 節點,然後立刻寫入,接着再讀取一個 DOM 節點。
  • 緩存 DOM 信息。
  • 不要一項一項地改變樣式,而是使用 CSS class 一次性改變樣式。
  • 使用documentFragment操作 DOM
  • 動畫使用absolute定位或fixed定位,這樣可以減少對其他元素的影響。
  • 只在必要時才顯示隱藏元素。
  • 使用window.requestAnimationFrame(),因爲它可以把代碼推遲到下一次重流時執行,而不是立即要求頁面重流。
  • 使用虛擬 DOM(virtual DOM)庫。

學習地址https://wangdoc.com/javascript/bom/engine.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章