前端跳槽面試總結之渲染機制、運行機制、頁面性能和錯誤監控

一、渲染機制

  1. 對於渲染機制,會從 DOCTYPE 及作用、瀏覽器渲染過程、重排 Reflow、重繪 Repaint和 佈局 Layout 這幾個方面。
  2. 對於 DOCTYPE 及作用,DTD 是文檔類型定義,是一系列的語法規則,用來定義 XMLXHTML 的文件類型。瀏覽器會使用它來判斷文檔類型,決定使用何種協議來解析,以及切換瀏覽器模式。 DOCTYPE 是用來聲明文檔類型和 DTD 規範的,一個主要的用途是文件的合法性驗證。如果文件代碼不合法,那麼瀏覽器解析時便會出一些差錯。
  3. 常見的 DOCTYPE,如下所示:
  • HTML 5<!DOCTYPE html>
  • HTML 4.01 Strict,這個 DTD 包含所有 HTML 元素和屬性,但不包括表象或過時的元素(如 font ),框架集是不允許的,<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  • HTML 4.01 Transitional,這個 DTD 包含所有 HTML 元素和屬性,包括表象或過時的元素(如 font ),框架集是不允許的,<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  1. 對於瀏覽器渲染過程,HTML 按照 HTML Parser 結合 DOM 形成 DOM TreeStyle Sheets 按照 CSS Parser 形成 Style RulesDOM TreeStyle Rules 形成 Render Tree 渲染樹,通過 Layout 佈局,Render Tree 計算每個節點的位置大小等信息,通過 Painting 繪製根據計算好的信息繪製整個頁面,最後 Display 呈現出來。
  2. 解析 HTML 過程中的問題,自上而下解析 HTML,逐漸構建起 DOM tree,遇到stylelink標籤,會下載解析樣式表,同時構建CSSOM tree,不會阻塞 html的解析。但是遇到script標籤,它會立即下載並執行得到的腳本,會阻塞HTML的解析。直到腳本里的同步代碼部分(settimeout等異步操作之外的代碼)執行完之後,再接着解析接下來的HTML。直到將整個HTML文檔的最後一個標籤解析完畢,DOM tree生成完畢。然後CSSOM treerender tree生成,開始渲染。雖然script的下載、執行均會阻塞HTML的解析,但DOM tree的生成是在文檔的最後一個標籤解析完之後才生成,那麼script標籤的在HTML中的位置對DOM tree完全生成的時間應該是沒有任何影響的。那爲什麼有把script標籤放在</body>閉合之前的說法?

解答:這是因爲實際上瀏覽器在正式渲染之前,會進行預渲染(first paint,怎麼翻譯的我忘了,反正可以理解爲預渲染)。那麼什麼時候預渲染呢:當瀏覽器認爲DOM tree生成得差不多了的時候。那什麼是差不多呢,解析到body部分的第一個script標籤之前。所以script如果放在head裏,會推遲預渲染; 如果放在body的一開頭,後面的一大堆標籤還沒解析,等於欺騙瀏覽器說已經“差不多了”,也就等於違背了設計預渲染的初衷,會造成整體渲染完畢的推遲。放在body中間也是一樣的道理,所以還是放在最尾巴上比較好。

  1. 對於重排 ReflowDOM 結構中的各個元素都有自己的盒子模型,這些都需要瀏覽器根據各種樣式來計算並根據計算結果將元素放到它該出現的位置,這個過程稱爲 reflow
  2. 觸發 reflow 的條件,如下所示:
  • 當你增加、刪除、修改 DOM 結點時,會導致 ReflowRepaint
  • 當你移動 DOM 的位置,或是搞個動畫的時候
  • 當你修改 CSS 樣式的時候
  • 當你 Resize 窗口的時候,但是移動端是沒有這個問題,或是滾動的時候
  • 當你修改網頁的默認字體時
  1. 對於重繪 Repaint,當各種盒子的時候、大小及其他的屬性,例如顏色、字體大小等都確定下來後,瀏覽器於是便把這些元素都按照各自的特性繪製了一遍,於是頁面的內容出現了,這個過程稱爲 repaint
  2. 觸發 repaint 的條件,如下所示:
  • DOM 改動
  • CSS 改動

二、運行機制

  1. 對於 JS 的運行機制,分爲 JS 的單線程、任務隊列和 Event Loop 這幾個方面。
  2. 對於 JS 的單線程,JavaScript 語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。javaScript 的單線程,與它的用途有關。作爲瀏覽器腳本語言,JavaScript 的主要用途是與用戶互動,以及操作 DOM。這決定了它只能是單線程,否則會帶來很複雜的同步問題。爲了利用多核 CPU 的計算能力,HTML5 提出 Web Worker 標準,允許 JavaScript 腳本創建多個線程,但是子線程完全受主線程控制,且不得操作 DOM。所以,這個新標準並沒有改變 JavaScript 單線程的本質。
  3. 對於任務隊列,單線程就意味着,所有任務需要排隊,前一個任務結束,纔會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等着。JS 引擎執行異步代碼而不用等待,是因有爲有 消息隊列和事件循環。消息隊列是消息隊列是一個先進先出的隊列,它裏面存放着各種消息。事件循環是事件循環是指主線程重複從消息隊列中取消息、執行的過程。
  4. 主線程只會做一件事情,就是從消息隊列裏面取消息、執行消息,再取消息、再執行。當消息隊列爲空時,就會等待直到消息隊列變成非空。而且主線程只有在將當前的消息執行完成後,纔會去取下一個消息。這種機制就叫做事件循環機制,取一個消息並執行的過程叫做一次循環。
  5. JS 中分爲兩種任務類型:macrotaskmicrotask,在 ECMAScript 中,microtask 稱爲 jobsmacrotask 可稱爲 taskmacrotask(又稱之爲宏任務),可以理解是每次執行棧執行的代碼就是一個宏任務。microtask(又稱爲微任務),可以理解是在當前 task 執行結束後立即執行的任務。宏任務就是說執行棧裏的每一個被執行的代碼就是一個宏任務,包括一個事件產生的回調執行,會在執行完畢一段代碼之後先對 dom 進行一次渲染,然後再執行下一個宏任務。微任務是再宏任務執行完畢之後立即執行的,他在 dom 重新渲染之前,微任務的相應速度比宏任務是要快的。
  6. 任務隊列就是等候執行的一系列任務,總結如下:
  • 任務隊列又分爲macro-task(宏任務)和micro-task(微任務);
  • macro-task大概包括:script(整體代碼),setTimeout,setInterval,setImmediate,I/O,UI rendering;
  • micro-task大概包括:process.nextTick,Promise,Object.observe(已廢棄),MutationObserver(html5新特性)
  • setTimeout/Promise 等我們稱之爲任務源。而進入任務隊列的是他們指定的具體執行任務。
  • 來自不同任務源的任務會進入到不同的任務隊列
  • 對於優先級,micro-task > macro-task
  • 對於micro-taskprocess.nextTick > Promise.then
  • 對於macro-tasksetTimeout > setImmediate
  1. 對於Event Loop 事件循環機制,JS 的運行機制,如下所示:
  • 執行棧執行宏任務
  • 執行棧沒有任務就去輪詢事件隊列
  • 如果執行期間遇到微任務,就添加到微任務隊列
  • 一個宏任務執行完畢後會立即執行當前微任務隊列的任務
  • 宏任務執行完畢後開始渲染
  • 然後開啓下一輪宏任務
  1. 對於異步任務,常用的有,如下所示:
  • setTimeoutsetInterval
  • DOM 事件
  • ES6 中的 Promise

三、頁面性能

  1. 題目:提升頁面性能的方法有哪些。
  2. 對於提升頁面性能,如下所示:
  • 資源壓縮合並,減少 HTTP 請求
  • 非核心代碼異步加載,異步加載的方式和異步加載的區別
  • 利用瀏覽器緩存,緩存的分類和緩存的原理
  • 使用 CDN
  • 預解析 DNS
  1. 對於異步加載,異步加載的方式主要分爲三種,動態腳本加載、deferasync,它們之間的區別,如下所示:
  • defer 是在 HTML 解析完以後才執行,如果是多個,按照加載的順序依次執行
  • async 是在加載完以後立即執行,如果是多個,執行順序和加載順序無關
  1. 對於瀏覽器緩存,主要分爲強緩存和協商緩存。
  2. 強緩存,不會向服務器發送請求,直接從緩存中讀取資源,在hrome控制檯的Network選項中可以看到該請求返回200的狀態碼,並且Size顯示from disk cachefrom memory cache。強緩存可以通過設置兩種 HTTP Header 實現:ExpiresCache-ControlCache-Conctrol的優先級比Expires高。強制緩存就是向瀏覽器緩存查找該請求結果,並根據該結果的緩存規則來決定是否使用該緩存結果的過程,強制緩存的情況主要有三種,如下所示:
  • 不存在該緩存結果和緩存標識,強制緩存失效,則直接向服務器發起請求(跟第一次發起請求一致)
  • 存在該緩存結果和緩存標識,但是結果已經失效,強制緩存失效,則使用協商緩存
  • 存在該緩存結果和緩存標識,且該結果沒有還沒有失效,強制緩存生效,直接返回該結果
  1. ExpiresCache-Control,如下所示:
  • Expires,緩存過期時間,用來指定資源到期的時間,是服務器端的具體的時間點。Expires=max-age + 請求時間,需要和Last-modified結合使用。ExpiresWeb服務器響應消息頭字段,在響應http請求時告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器緩存取數據,而無需再次請求。ExpiresHTTP/1 的產物,受限於本地時間,如果修改了本地時間,可能會造成緩存失效。Expires: Wed, 22 Oct 2018 08:41:00 GMT表示資源會在 Wed, 22 Oct 2018 08:41:00 GMT 後過期,需要再次請求。ExpiresHTTP/1.0控制網頁緩存的字段,其值爲服務器返回該請求的結果緩存的到期時間,即再次發送請求時,如果客戶端的時間小於Expires的值時,直接使用緩存結果。到了HTTP/1.1Expires已經被Cache-Control替代,原因在於Expires控制緩存的原理是使用客戶端的時間與服務端返回的時間做對比,如果客戶端與服務端的時間由於某些原因(時區不同;客戶端和服務端有一方的時間不準確)發生誤差,那麼強制緩存直接失效,那麼強制緩存存在的意義就毫無意義。
  • Cache-Control,在HTTP/1.1中,Cache-Control是最重要的規則,主要用於控制網頁緩存。比如當Cache-Control:max-age=300時,則代表在這個請求正確返回時間(瀏覽器也會記錄下來)的5分鐘內再次加載資源,就會命中強緩存。Cache-Control 可以在請求頭或者響應頭中設置,並且可以組合使用多種指令,如下所示:
    • public:所有內容都將被緩存(客戶端和代理服務器都可緩存)
    • private:所有內容只有客戶端可以緩存,Cache-Control的默認取值
    • no-cache:客戶端緩存內容,但是是否使用緩存則需要經過協商緩存來驗證決定,設置了no-cache之後,並不是說瀏覽器就不再緩存數據,只是瀏覽器在使用緩存數據時,需要先確認一下數據是否還跟服務器保持一致
    • no-store:所有內容都不會被緩存,即不使用強制緩存,也不使用協商緩存
    • max-age=xxx (xxx is numeric):緩存內容將在xxx秒後失效
    • max-stale:能容忍的最大過期時間
    • min-fresh:能夠容忍的最小新鮮度
  1. ExpiresCache-Control兩者對比,區別就在於 Expireshttp1.0的產物,Cache-Controlhttp1.1的產物,兩者同時存在的話,Cache-Control優先級高於Expires;在某些不支持HTTP1.1的環境下,Expires就會發揮用處。所以Expires其實是過時的產物,現階段它的存在只是一種兼容性的寫法。強緩存判斷是否緩存的依據來自於是否超出某個時間或者某個時間段,而不關心服務器端文件是否已經更新,這可能會導致加載文件不是服務器端最新的內容,那我們如何獲知服務器端內容是否已經發生了更新呢?此時我們需要用到協商緩存策略。
  2. 協商緩存,協商緩存就是強制緩存失效後,瀏覽器攜帶緩存標識向服務器發起請求,由服務器根據緩存標識決定是否使用緩存的過程,可以通過設置兩種 HTTP Header 實現:Last-ModifiedETag,情況如下所示:
  • 協商緩存生效,返回304Not Modified
  • 協商緩存失效,返回200和請求結果
  1. Last-ModifiedIf-Modified-Since,如下所示:
  • 瀏覽器在第一次訪問資源時,服務器返回資源的同時,在response header中添加 Last-Modifiedheader,值是這個資源在服務器上的最後修改時間,瀏覽器接收後緩存文件和header,如 Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
  • 瀏覽器下一次請求這個資源,瀏覽器檢測到有 Last-Modified這個header,於是添加If-Modified-Since這個header,值就是Last-Modified中的值;服務器再次收到這個資源請求,會根據 If-Modified-Since 中的值與服務器中這個資源的最後修改時間對比,如果沒有變化,返回304和空的響應體,直接從緩存讀取,如果If-Modified-Since的時間小於服務器中這個資源的最後修改時間,說明文件有更新,於是返回新的資源文件和200
  • Last-Modified 存在一些弊端,如果本地打開緩存文件,即使沒有對文件進行修改,但還是會造成 Last-Modified 被修改,服務端不能命中緩存導致發送相同的資源。因爲 Last-Modified 只能以秒計時,如果在不可感知的時間內修改完成文件,那麼服務端會認爲資源還是命中了,不會返回正確的資源
  • 根據文件修改時間來決定是否緩存尚有不足,能否可以直接根據文件內容是否修改來決定緩存策略,所以在 HTTP / 1.1 出現了 ETagIf-None-Match
  1. ETagIf-None-Match,如下所示:
  • Etag是服務器響應請求時,返回當前資源文件的一個唯一標識(由服務器生成),只要資源有變化,Etag就會重新生成
  • 瀏覽器在下一次加載資源向服務器發送請求時,會將上一次返回的Etag值放到request header裏的If-None-Match裏,服務器只需要比較客戶端傳來的If-None-Match跟自己服務器上該資源的ETag是否一致,就能很好地判斷資源相對客戶端而言是否被修改過了。如果服務器發現ETag匹配不上,那麼直接以常規GET 200回包形式將新的資源(當然也包括了新的ETag)發給客戶端;如果ETag是一致的,則直接返回304知會客戶端直接使用本地緩存即可
  1. Last-ModifiedEtag 的比較,如下所示:
  • 在精確度上,Etag要優於Last-ModifiedLast-Modified的時間單位是秒,如果某個文件在1秒內改變了多次,那麼他們的Last-Modified其實並沒有體現出來修改,但是Etag每次都會改變確保了精度;如果是負載均衡的服務器,各個服務器生成的Last-Modified也有可能不一致
  • 在性能上,Etag要遜於Last-Modified,畢竟Last-Modified只需要記錄時間,而Etag需要服務器通過算法來計算出一個hash
  • 在優先級上,服務器校驗優先考慮Etag
  1. 緩存機制,強制緩存優先於協商緩存進行,若強制緩存(Expires和Cache-Control)生效則直接使用緩存,若不生效則進行協商緩存(Last-Modified / If-Modified-Since和Etag / If-None-Match),協商緩存由服務器決定是否使用緩存,若協商緩存失效,那麼代表該請求的緩存失效,返回200,重新返回資源和緩存標識,再存入瀏覽器緩存中;生效則返回304,繼續使用緩存。如果什麼緩存策略都沒設置,那麼瀏覽器會採用一個啓發式的算法,通常會取響應頭中的 Date 減去 Last-Modified 值的 10% 作爲緩存時間。
  2. 實際場景應用緩存策略,如下所示:
  • 頻繁變動的資源,Cache-Control: no-cache。對於頻繁變動的資源,首先需要使用Cache-Control: no-cache 使瀏覽器每次都請求服務器,然後配合 ETag 或者 Last-Modified 來驗證資源是否有效。這樣的做法雖然不能節省請求數量,但是能顯著減少響應數據大小
  • 不常變化的資源,Cache-Control: max-age=31536000。通常在處理這類資源時,給它們的 Cache-Control 配置一個很大的 max-age=31536000 (一年),這樣瀏覽器之後請求相同的 URL 會命中強制緩存。而爲了解決更新的問題,就需要在文件名(或者路徑)中添加 hash, 版本號等動態字符,之後更改動態字符,從而達到更改引用 URL 的目的,讓之前的強制緩存失效
  1. 用戶行爲對瀏覽器緩存的影響,用戶在瀏覽器如何操作時,會觸發怎樣的緩存策略,如下所示:
  • 打開網頁,地址欄輸入地址: 查找 disk cache 中是否有匹配。如有則使用;如沒有則發送網絡請求
  • 普通刷新 (F5):因爲 TAB 並沒有關閉,因此 memory cache 是可用的,會被優先使用(如果匹配的話)。其次纔是 disk cache
  • 強制刷新 (Ctrl + F5):瀏覽器不使用緩存,因此發送的請求頭部均帶有 Cache-control: no-cache(爲了兼容,還帶了 Pragma: no-cache),服務器直接返回 200 和最新內容

四、錯誤監控

  1. 對於錯誤監控,分爲前端錯誤的分類、錯誤的捕獲方式和上報錯誤的基本原理。
  2. 對於前端錯誤的分類,主要是分爲即時運行出現的代碼錯誤和資源加載錯誤。
  3. 對於錯誤的捕獲方式,如下所示:
  • 即時運行錯誤的捕獲方式,有 try...catchwindow.onerror
  • 資源加載錯誤,有 object.onerrorperformance.getEntries()Error 事件捕獲
  1. 問題:跨域的 JS 運行錯誤可以捕獲嗎,錯誤提示什麼,應該怎麼處理?

解答:可以捕獲,錯誤信息爲 Script error,錯誤詳情爲 null,處理方式爲 在 script標籤增加 crossorigin 屬性,設置 JS 資源響應頭 Access-Control-Allow-Origin

  1. 對於上報錯誤的基本原理,如下所示:
  • 採用 Ajax 通信的方式上報
  • 利用 Image 對象上報
  1. 錯誤監控的代碼,如下所示:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>錯誤監控</title>
    <script type="text/javascript">
      window.addEventListener('error', function (e) {
          console.log('捕獲', e);
      }, false);
    </script>
  </head>
  <body>
    <script src="//badu.com/test.js" charset="utf-8"></script>
    <script type="text/javascript">
      (new Image()).src = 'http://baidu.com/tesjk?r=tksjk';
    </script>
  </body>
</html>

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