前端面試八股文

1.HTTP和HTTPS的基本概念

http:是一個客戶端和服務端請求和應答的標準(TCP),用於從www伺服器傳輸超文本到本地瀏覽器的超文本傳輸協議。

https:是以安全為目標的HTTP通道,即HTTP下加入SSL層進行加密。其作用是:建立一個資訊安全通道,確保數據的傳輸,確保網站的真實性。

補充:SSL是洋文「Secure Sockets Layer」的縮寫,中文叫做「安全套接層」。

2.HTTP和HTTPS的區別及優缺點?

  1. HTTPS 協議需要 CA 證書,費用較高;而HTTP 協議不需要;
  2. HTTP是超文本傳輸協議,資訊是明文傳輸;HTTPS協議要比HTTP協議安全,HTTPS是具有安全性的SSL加密傳輸協議,可防止數據在傳輸過程中被竊取、改變,確保數據的完整性(當然這種安全性並非絕對的,低於更深的Web安全問題,次數暫且不表);
  3. HTTP協議的默認埠為80;HTTPS默認埠為443;
  4. HTTP 協議連接很簡單,是無狀態的;HTTPS 協議是有SSL 和HTTP協議構建的可進行加密傳輸、身份認證的網路協議,比HTTP 更加安全;

3.HTTPS協議工作原理

如何保證資訊的安全性:對稱加密、非對稱加密、數字證書與數字簽名

  1. 客戶端申請HTTPS通訊,建立SSL鏈接
  2. 伺服器響應並向客戶端傳遞證書(證書中包含了密鑰)
  3. 客戶端驗證證書,獲取公鑰生成對稱加密密鑰,用公鑰加密後傳給伺服器
  4. 伺服器收到消息,用私鑰解密,拿出對稱密鑰,並通知客戶端,SSL通道建立完成,HTTPS通道也建立完成。
  5. 共享密鑰交換成功,HTTPS通訊建立後,客戶端和服務端利用共享密鑰加密通訊
  6. 客戶端斷開鏈接

一定要用https嗎?

https那麼的安全,是不是我們在什麼場景下都要去使用https進行通訊呢?答案是否定的。

1)https雖然提供了消息安全傳輸的通道,但是每次消息的加解密十分耗時,消息系統資源。所以,除非在一些對安全性比較高的場景下,比如銀行系統,購物系統中我們必須要使用https進行通訊,其他一些對安全性要求不高的場景,我們其實沒必要使用https。

2)使用https需要使用到數字證書,但是一般權威機構頒發的數字證書都是收費的,而且價格也是不菲的,所以對於一些個人網站特別是學生來講,如果對安全性要求不高,也沒必要使用https。

參考:https的工作原理

4.TCP三次握手

第一次握手:建立連接,客戶端發送SYN包(syn=j)(是TCP/IP建立連接時使用的握手訊號。),隨後客戶端進入SYN-SENT階段,等待服務端確認。

第二次握手:服務端收到syn包並確認客戶的SYN,同時也發送一個自己的SYN包,隨後伺服器端進入SYN-RCVD階段。

第三次握手:客戶端接收到來自伺服器端的確認收到數據的SYN+ACK包,明確了從客戶端到伺服器的數據傳輸是正常的,結束SYN-SENT階段,並向伺服器發送確認包ACK。隨後客戶端進入ESTABLISHED階段。伺服器收到來自客戶端的「確認收到伺服器數據」的TCP報文之後,明確了從伺服器到客戶端的數據傳輸是正常的。完成三次握手。

為什麼要進行第三次握手?

為了防止伺服器端開啟一些無用的連接增加伺服器開銷以及防止已失效的連接請求報文段突然又傳送到了服務端,因而產生錯誤。

由於網路傳輸是有延時的(要通過網路光纖和各種中間代理伺服器),在傳輸的過程中,比如客戶端發起了SYN=1創建連接的請求(第一次握手)。如果伺服器端就直接創建了這個連接並返回包含SYN、ACK和Seq等內容的數據包給客戶端,這個數據包因為網路傳輸的原因丟失了,丟失之後客戶端就一直沒有接收到伺服器返回的數據包。客戶端可能設置了一個超時時間,時間到了就關閉了連接創建的請求。再重新發出創建連接的請求,而伺服器端是不知道的,如果沒有第三次握手告訴伺服器端客戶端收的到伺服器端傳輸的數據的話,伺服器端是不知道客戶端有沒有接收到伺服器端返回的資訊的。

5.TCP四次揮手

  • 首先客戶端想要釋放連接,向伺服器端發送一段TCP報文,隨後客戶端進入FIN-WAIT-1階段,即半關閉階段,並且停止在客戶端到伺服器端方向上發送數據,但是客戶端仍然能接收從伺服器端傳輸過來的數據。
  • 伺服器端接收到從客戶端發出的連接釋放報文之後,確認了客戶端想要釋放連接,發出確認報文,表示「接收到客戶端發送的釋放連接的請求」。

前"兩次揮手"既讓伺服器端知道了客戶端想要釋放連接,也讓客戶端知道了伺服器端了解了自己想要釋放連接的請求。於是,可以確認關閉客戶端到伺服器端方向上的連接了

  • 伺服器端自從發出ACK確認報文之後,經過CLOSED-WAIT階段,做好了釋放伺服器端到客戶端方向上的連接準備,向客戶端發出釋放報文,等待客戶端的確認,隨後伺服器端結束CLOSE-WAIT階段,進入LAST-ACK階段。並且停止在伺服器端到客戶端的方向上發送數據,但是伺服器端仍然能夠接收從客戶端傳輸過來的數據。
  • 客戶端收到伺服器的連接釋放報文後,向伺服器端發送確認報文,伺服器端收到從客戶端發出的TCP報文之後結束LAST-ACK階段,進入CLOSED階段。由此正式確認關閉伺服器端到客戶端方向上的連接。

簡單來說

第一次揮手:客戶端告訴服務端我要斷開連接了

第二次揮手:服務端告訴客戶端我知道你要斷開連接了,並告訴客戶端你可以斷開了

第三次揮手:服務端做好了斷開準備,並想客戶端發送斷開請求,等到客戶端確認

第四次揮手:客戶端接收到服務端的可以釋放的訊號,向服務端發送確認斷開的訊號,服務端收到後就關閉連接。

參考:詳解 TCP 連接的「 三次握手 」與「 四次揮手

6.cookie、sessionStorage、localStorage的區別

相同點:都是在開發中用到的臨時存儲客戶端會話資訊或者數據的方法

不同點:

1.存儲的時間有效期不同

  • cookie的有效期是可以設置的,默認的情況下是關閉瀏覽器後失效
  • sessionStorage的有效期是僅保持在當前頁面,關閉當前會話頁或者瀏覽器後就會失效
  • localStorage的有效期是在不進行手動刪除的情況下是一直有效的

2.存儲的大小不同

  • cookie的存儲是4kb左右,存儲量較小,一般頁面最多存儲20條左右資訊
  • localStorage和sessionStorage的存儲容量是5Mb(官方介紹,可能和瀏覽器有部分差異性)

3.與服務端的通訊

  • cookie會參與到與服務端的通訊中,一般會攜帶在http請求的頭部中,例如一些關鍵密匙驗證等。
  • localStorage和sessionStorage是單純的前端存儲,不參與與服務端的通訊

參考:cookie、localStorage和sessionStorage三者的區別

7.Vuex 和 localStorage 的區別

  1. 最重要的區別:vuex 存儲在記憶體中localstorage 則以文件的方式存儲在本地,只能存儲字元串類型的數據,存儲對象需要 JSON 的 stringify 和parse 方法進行處理。讀取記憶體比讀取硬碟速度要快。
  2. 應用場景 Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式。它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。vuex 用於組件之間的傳值。localstorage 是本地存儲,是將數據存儲到瀏覽器的方法,一般是在跨頁面傳遞數據時使用 。 Vuex 能做到數據的響應式,localstorage 不能。
  3. 永久性 刷新頁面時 vuex 存儲的值會丟失,localstorage 不會。注意:對於不變的數據確實可以用 localstorage 可以代替vuex,但是當兩個組件共用一個數據源(對象或數組)時,如果其中一個組件改變了該數據源,希望另一個組件響應該變化時,localstorage無法做到,原因就是區別 1。

8.瀏覽器如何渲染網頁

  1. 解析HTML生成DOM樹 - 渲染引擎首先解析HTML文檔,生成DOM樹
  2. 構建Render樹 - 接下來不管是內聯式,外聯式還是嵌入式引入的CSS樣式會被解析生成CSSOM樹,根據DOM樹與CSSOM樹生成另外一棵用於渲染的樹-渲染樹(Render tree),
  3. 布局Render樹 - 然後對渲染樹的每個節點進行布局處理,確定其在螢幕上的顯示位置
  4. 繪製Render樹 - 最後遍歷渲染樹並用UI後端層將每一個節點繪製出來

解析HTML文件,創建DOM樹,自上而下,如果遇到樣式(link、style)與腳本(script)都會阻塞

補充1:輸入url後發生了什麼?

  1. DNS域名解析
  2. 建立TCP連接(三次握手)
  3. 發送HTTP請求
  4. 伺服器處理請求,返迴響應結果
  5. 關閉TCP連接(四次揮手)
  6. 瀏覽器解析HTML渲染頁面,構建DOM樹

詳解:從輸入url到頁面載入完成發生了什麼?

補充2:避免JavaScript 阻塞JavaScript 腳本延遲載入的方式有哪些?

  1. defer 屬性:給 js 腳本添加 defer 屬性,這個屬性會讓腳本的載入與文檔的解析同步解析,然後在文檔解析完成後再執行這個腳本文件,這樣的話就能使頁面的渲染不被阻塞。多個設置了defer屬性 14 的腳本按規範來說最後是順序執行的,但是在一些瀏覽器中可能不是這樣。
  2. async 屬性:給 js 腳本添加 async 屬性,這個屬性會使腳本非同步載入,不會阻塞頁面的解析過程,但是當腳本載入完成後立即執行js腳本,這個時候如果文檔沒有解析完成的話同樣會阻塞。多個async屬性的腳本的執行順序是不可預測的,一般不會按照程式碼的順序依次執行。
  3. 動態創建 DOM 方式:動態創建 DOM 標籤的方式,可以對文檔的載入事件進行監聽,當文檔載入完成後再動態的創建script 標籤來引入js 腳本。
  4. 使用 setTimeout 延遲方法:設置一個定時器來延遲載入js 腳本文件
  5. 讓 JS 最後載入:將 js 腳本放在文檔的底部,來使js 腳本儘可能的在最後來載入執行。

9.重繪與重排相關

重繪與重排的區別

重排:當渲染樹中部分或者全部元素的尺寸、結構或者屬性發生變化時,瀏覽器會重新渲染部分或者全部文檔的過程就稱為重排/迴流。

重繪:當頁面中某些元素的樣式發生變化,但是不會影響其在文檔流中的位置時,瀏覽器就會對元素進行重新繪製,這個過程就是重繪。

如何觸發重繪與重排

下面這些操作會導致重排/迴流:

頁面的首次渲染 、瀏覽器的窗口大小發生變化 、元素的內容發生變化 、元素的尺寸或者位置發生變化、 元素的字體大小發生變化、 激活 CSS 偽類、 查詢某些屬性或者調用某些方法 、添加或者刪除可見的 DOM 元素

在觸發迴流(重排)的時候,由於瀏覽器渲染頁面是基於流式布局的,所以當觸發迴流時,會導致周圍的 DOM 元素重新排列,它的影響範圍有兩種: 全局範圍:從根節點開始,對整個渲染樹進行重新布局局部範圍:對渲染樹的某部分或者一個渲染對象進行重新布局

下面這些操作會導致重繪:

color、background 相關屬性:background-color、background-image等 outline 相 關 屬 性 : outline-color 、outline-width、text-decoration border-radius、visibility、box-shadow 注意: 當觸發重排/迴流時,一定會觸發重繪,但是重繪不一定會引發重排/迴流。

如何避免重繪或重排

  1. 用transform做形變和位移可以減少reflow
  2. 避免逐個修改節點樣式,盡量一次性修改
  3. 使用DocumentFragment將需要多次修改的DOM元素快取,最後一次性append到真實DOM中渲染
  4. 可以將需要多次修改的DOM元素設置display:none,操作完再顯示。(因為隱藏元素不在render樹內,因此修改隱藏元素不會觸發迴流重繪)
  5. 避免多次讀取某些屬性
  6. 通過絕對位移將複雜的節點元素脫離文檔流,形成新的Render Layer,降低迴流成本
  7. 提升為合成層     將元素提升為合成層有以下優點:
    • 合成層的點陣圖,會交由 GPU 合成,比 CPU 處理要快
    • 當需要 repaint 時,只需要 repaint 本身,不會影響到其他的層
    • 對於 transform 和 opacity 效果,不會觸發 layout 和 paint

提升合成層的最好方式是使用 CSS 的 will-change 屬性:

#target{
  will-change: transform;
}

參考:什麼是重排(迴流)和重繪?如何避免他們?

注意:

  • 直接使用opacity即觸發重繪,又觸發重排(GPU底層設計如此!)。
  • opacity配合圖層使用,即不觸發重繪也不觸發重排。(原因:透明度的改變時,GPU在繪畫時只是簡單的降低之前已經畫好的紋理的alpha值來達到效果,並不需要整體的重繪。不過這個前提是這個被修改opacity本身必須是一個圖層。)
參考:圖層與重排重繪

 10.瀏覽器的快取機制 強快取、協商快取

瀏覽器與伺服器通訊的方式為應答模式,即是:瀏覽器發起HTTP請求-伺服器響應請求。那麼瀏覽器第一次向伺服器發起該請求後拿到請求結果,會根據響應報文中HTTP頭的快取標識,決定是否快取結果,是則將請求和快取標識存入瀏覽器快取中,簡單的過程如下圖:

由上圖我們可以知道:

  • 瀏覽器每次發起請求,都會先在瀏覽器快取中查找該請求的結果以及快取標識
  • 瀏覽器每次拿到返回的請求結果都會將該結果和快取標識存入瀏覽器快取中

以上兩點結論就是瀏覽器快取機制的關鍵,他確保了每個請求的快取存入與讀取,只要我們再理解瀏覽器快取的使用規則,那麼所有的問題就迎刃而解了,本文也將圍繞著這點進行詳細分析。為了方便大家理解,這裡我們根據是否需要向伺服器重新發起HTTP請求將快取過程分為兩個部分,分別是強制快取和協商快取 。

強制快取

強制快取就是向瀏覽器快取查找該請求結果,並根據該結果的快取規則來決定是否使用該快取結果的過程,強制快取的情況主要有三種(暫不分析協商快取過程),如下:

  • 不存在該快取結果和快取標識,強制快取失效,則直接向伺服器發起請求(跟第一次發起請求一致)
  • 存在該快取結果和快取標識,但該結果已失效,強制快取失效,則使用協商快取
  • 存在該快取結果和快取標識,且該結果尚未失效,強制快取生效,直接返回該結果
那麼強制快取的快取規則是什麼?

當瀏覽器向伺服器發起請求時,伺服器會將快取規則放入HTTP響應報文的HTTP頭中和請求結果一起返回給瀏覽器,控制強制快取的欄位分別是Expires和Cache-Control,其中Cache-Control優先順序比Expires高。

協商快取

 協商快取就是強制快取失效後,瀏覽器攜帶快取標識向伺服器發起請求,由伺服器根據快取標識決定是否使用快取的過程,同樣,協商快取的標識也是在響應報文的HTTP頭中和請求結果一起返回給瀏覽器的,控制協商快取的欄位分別有:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的優先順序比Last-Modified / If-Modified-Since高。其主要有以下兩種情況:

  • 協商快取生效,返回304
  • 協商快取失效,返回200和請求結果結果

參考:徹底理解瀏覽器的快取機制

11.防抖和節流

防抖:是指在事件被觸發 n 秒後再執行回調,如果在這n 秒內事件又被觸發,則重新計時。這可以使用在一些點擊請求的事件上,避免因為用戶的多次點擊向後端發送多次請求。

節流:是指規定一個單位時間,在這個單位時間內,只能有一次觸發事件的回調函數執行,如果在同一個單位時間內某事件被觸發多次,只有一次能生效。節流可以使用在 scroll 函數的事件監聽上,通過事件節流來降低事件調用的頻率。

防抖和節流的應用場景

防抖
1.登錄、發簡訊等按鈕避免用戶點擊太快,以致於發送了多次請求,需要防抖。
2.調整瀏覽器窗口大小時,resize 次數過於頻繁,造成計算過多。
3.文本編輯器實時保存,當無任何更改操作一秒後進行保存。
4.DOM 元素的拖拽功能實現。
5.計算滑鼠移動的距離。
6.Canvas 模擬畫板功能。
7.搜索聯想。
節流
1.scroll 事件,每隔一秒計算一次位置資訊等。
2.瀏覽器播放事件,每個一秒計算一次進度資訊等。
3.input框實時搜索並發送請求展示下拉列表,每隔一秒發送一次請求 (也可做防抖)。

實現節流函數和防抖函數

函數防抖的實現:

        function debounce(fn, wait) {
            var timer = null;
            return function () {
                var context = this,
                    args = [...arguments];
                // arguments是function里特定的對象之一,指的是function的參數對象,按題意里的,當然對應的就是它所在的那一層function。即return function(){};
                //如果此時存在定時器的話,則取消之前的定時器重新計時
                if (timer) {
                    clearTimeout(timer)
                    timer = null
                }
                //設計定時器,使事件間隔指定時間後執行
                timer = setTimeout(() => {
                    fn.apply(context, args);
                }, wait)
            }
        }

        function sayHi() {
            console.log("防抖成功");
        }
        var inp = document.getElementById("inp");
        inp.addEventListener("input", debounce(sayHi, 2000)); //防抖

函數節流的實現:

        //時間戳版
        function throttle(fn, delay) {
            var preTime = Date.now();
            return function () {
                var context = this, args = [...arguments], nowTime = Date.now();
                //如果兩次時間間隔超過了指定時間,則執行函數。
                if (nowTime - preTime >= delay) {
                    preTime = Date.now();
                    return fn.apply(context, args);
                }
            }
        }

        //定時器版
        function throttle2(fun, awit) {
            let timeOut = null;
            return function () {
                let context = this, args = [...arguments];
                if (!timeOut) {
                    timeOut = setTimeout(() => {
                        fun.apply(context, args);
                        timeOut = null
                    }, awit)
                }
            }
        }

        function sayHi() {
            console.log(e.target.innerWidth,e.target.innerHeight);
        }

        window.addEventListener('resize',throttle2(sayHi,1000))