高頻前端面試題彙總之瀏覽器原理篇

語言: CN / TW / HK

theme: jzman

本人近期在準備找份前端實習,便整理了一下高頻的前端面試題,分享給大家一起來學習。這裡的面試題答案都比較簡潔扼要,如果需要詳細瞭解的,可以自行查閱相關的資料。如有問題,歡迎指正!

一、瀏覽器安全

1.  什麼是 XSS 攻擊?

XSS 攻擊指的是跨站指令碼攻擊,攻擊者通過在網站注入惡意指令碼,使之在使用者的瀏覽器上執行,從而盜取使用者的資訊。其本質是因為網站沒有對惡意程式碼進行過濾,與正常的程式碼混合在一起了,瀏覽器沒有辦法分辨哪些指令碼是可信的,從而導致了惡意程式碼的執行。

攻擊者可以通過這種攻擊方式進行以下操作:

  • 獲取頁面的資料,如DOM、cookie
  • 佔用伺服器資源,從而使使用者無法訪問伺服器
  • 破壞頁面結構
  • 流量劫持(將連結指向某網站)

XSS 可以分為儲存型、反射型和 DOM 型:

  • 儲存型指的是惡意指令碼會儲存在伺服器上,當瀏覽器請求資料時,指令碼從伺服器傳回並執行。
  • 反射型指的是攻擊者誘導使用者訪問一個帶有惡意程式碼的 URL 後,伺服器端接收資料處理後把帶有惡意程式碼的資料傳送到瀏覽器端,瀏覽器端解析這段帶有 XSS 程式碼的資料後當做指令碼執行。 
  • DOM 型指的通過修改頁面的 DOM 節點形成的 XSS。

2. 如何防禦 XSS 攻擊?

  • cookie 使用 http-only,使得指令碼無法獲取
  • 對 v-html 和 innerHTML 載入的資訊進行轉義
  • 使用 CSP ,CSP 的本質是建立一個白名單,告訴瀏覽器哪些外部資源可以載入和執行。通常有兩種方式來開啟 CSP,一種是設定 HTTP 首部中的 Content-Security-Policy,一種是設定 meta 標籤的方式。

3. 什麼是 CSRF 攻擊?

CSRF 攻擊指的是跨站請求偽造攻擊,攻擊者誘導使用者進入一個第三方網站,然後該網站向被攻擊網站傳送跨站請求。如果使用者在被攻擊網站中儲存了登入狀態,那麼攻擊者就可以利用這個登入狀態,繞過後臺的使用者驗證,冒充使用者向伺服器執行一些操作。

CSRF 攻擊的本質是利用 cookie 會在同源請求中攜帶傳送給伺服器的特點,以此來實現使用者的冒充。

常見的 CSRF 攻擊有三種:

  • GET 型別的 CSRF 攻擊,比如在網站中的一個 img 標籤裡構建一個請求,當用戶開啟這個網站的時候就會自動發起提交。
  • POST 型別的 CSRF 攻擊,比如構建一個表單,然後隱藏它,當用戶進入頁面時,自動提交這個表單。
  • 連結型別的 CSRF 攻擊,比如在 a 標籤的 href 屬性裡構建一個請求,然後誘導使用者去點選。

4. 如何防禦 CSRF 攻擊?

  • 進行同源檢測:伺服器根據 http 請求頭中 origin 或者 referer 資訊來判斷請求是否為允許訪問的站點,從而對請求進行過濾。這種方式的缺點是有些情況下 referer 可以被偽造,同時還會把搜尋引擎的連結也給遮蔽了。
  • 使用 CSRF Token 進行驗證:伺服器向用戶返回一個隨機數 Token ,當網站再次發起請求時,在請求引數中加入伺服器端返回的 token ,然後伺服器對這個 token 進行驗證。但是我們需要給網站中的所有請求都新增上這個 token,操作比較繁瑣。
  • 對 Cookie 進行雙重驗證:伺服器在使用者訪問網站頁面時,向請求域名注入一個Cookie,內容為隨機字串,然後當用戶再次向伺服器傳送請求的時候,從 cookie 中取出這個字串,新增到 URL 引數中,然後伺服器通過對 cookie 中的資料和引數中的資料進行比較,來進行驗證。的,那麼這種方式會失效。同時這種方式不能做到子域名的隔離。
  • 在設定 cookie 屬性的時候設定 Samesite ,限制 cookie 不能作為被第三方使用用。

5. 什麼是中間人攻擊?

是指攻擊者與通訊的兩端分別建立獨立的聯絡, 並交換其所收到的資料, 使通訊的兩端認為他們正在通過⼀個私密的連線與對方直接對話, 但事實上整個會話都被攻擊者完全控制,攻擊者可以攔截通訊雙⽅的通話並插⼊新的內容。

攻擊過程如下:

  • 客戶端傳送請求到服務端,請求被中間⼈截獲
  • 伺服器向客戶端傳送公鑰
  • 中間⼈截獲公鑰,保留在自己手上。然後自己⽣成⼀個偽造的公鑰,發給客戶端
  • 客戶端收到偽造的公鑰後,⽣成加密hash值發給伺服器
  • 中間⼈獲得加密hash值,⽤自己的私鑰解密獲得真祕鑰,同時⽣成假的加密hash值,發給伺服器
  • 伺服器⽤私鑰解密獲得假金鑰,然後加密資料傳輸給客戶端

6. 如何防範中間人攻擊?

  • 採用傳輸加密:SSL 和 TLS 可以阻止攻擊者使用和分析網路流量
  • 安裝 DAM 資料庫活動監控:DAM 可以監控資料庫活動,檢測篡改資料

7. 有哪些可能引起前端安全的問題?

  • 跨站指令碼攻擊 (XSS)
  • 跨站請求偽造攻擊(CSRF)
  • 惡意第三⽅庫
  • iframe的濫⽤

8. 網路劫持有哪幾種,如何防範?

  • DNS劫持: (輸⼊京東被強制跳轉到淘寶這就屬於dns劫持)

  • HTTP劫持: (訪問⾕歌但是⼀直有貪玩藍⽉的⼴告),由於http明⽂傳輸,運營商會修改你的http響應內容(即加⼴告)

DNS劫持由於涉嫌違法,已經被監管起來,現在很少會有DNS劫持,⽽http劫持依然⾮常盛⾏,最有效的辦法就是全站HTTPS,將HTTP加密,這使得運營商⽆法獲取明⽂,就⽆法劫持你的響應內容。

二、瀏覽器快取

1. 對瀏覽器的快取機制的理解

  • 瀏覽器第一次載入資源,伺服器返回 200,瀏覽器從伺服器下載資原始檔,並快取資原始檔與 response header,以供下次載入時對比使用;
  • 下一次載入資源時,由於強制快取優先順序較高,先比較當前時間與上一次返回 200 時的時間差,如果沒有超過 cache-control 設定的 max-age,則沒有過期,並命中強快取,直接從本地讀取資源。如果瀏覽器不支援HTTP1.1,則使用 expires 頭判斷是否過期;
  • 如果資源已過期,則表明強制快取沒有被命中,則開始協商快取,向伺服器傳送帶有 If-None-Match 和 If-Modified-Since 的請求;
  • 伺服器收到請求後,優先根據 Etag 的值判斷被請求的檔案有沒有做修改,Etag 值一致則沒有修改,命中協商快取,返回 304;如果不一致則有改動,直接返回新的資原始檔帶上新的 Etag 值並返回 200;
  • 如果伺服器收到的請求沒有 Etag 值,則將 If-Modified-Since 和被請求檔案的最後修改時間做比對,一致則命中協商快取,返回 304;不一致則返回新的 last-modified 和檔案並返回 200;

2. 協商快取和強快取的區別

強快取: 使用強快取策略時,如果快取資源有效,則直接使用快取資源,不必再向伺服器發起請求。強快取策略可以通過兩種方式來設定,分別是 http 頭資訊中的 Expires 屬性和 Cache-Control 屬性。

伺服器通過在響應頭中新增 Expires 屬性,來指定資源的過期時間。在過期時間以內,該資源可以被快取使用,不必再向伺服器傳送請求。

Expires 是 http1.0 中的方式,因為它的一些缺點,在 HTTP 1.1 中提出了一個新的頭部屬性就是 Cache-Control 屬性,它提供了對資源的快取的更精確的控制。一般來說只需要設定其中一種方式就可以實現強快取策略,當兩種方式一起使用時,Cache-Control 的優先順序要高於 Expires。Cache-Control 可設定的欄位:

js public: 表示可以被任何物件快取 private: 只能被使用者瀏覽器快取,不允許任何代理伺服器快取 no-cache:需要先和服務端確認返回的資源是否發生了變化,如果未發生變化,則直接使用快取好的資源 no-store:禁止任何快取,每次都會向服務端發起新的請求,拉取最新的資源 max-age=:設定快取的最大有效期,單位為秒 s-maxage=:僅適用於共享快取(CDN),優先順序高於max-age或者Expires頭

協商快取: 如果命中強制快取,我們無需發起新的請求,直接使用快取內容;如果沒有命中強制快取,如果設定了協商快取,這個時候協商快取就會發揮作用了。命中協商快取的條件有兩個: max-age=xxx 過期了、值為 no-store

使用協商快取策略時,會先向伺服器傳送一個請求,如果資源沒有發生修改,則返回一個 304 狀態,讓瀏覽器使用本地的快取副本。如果資源發生了修改,則返回修改後的資源。協商快取也可以通過兩種方式來設定,分別是 http 頭資訊中的 Etag 和 Last-Modified屬性。

(1)伺服器通過在響應頭中新增 Last-Modified 屬性來指出資源最後一次修改的時間,當瀏覽器下一次發起請求時,會在請求頭中新增一個 If-Modified-Since 的屬性,屬性值為上一次資源返回時的 Last-Modified 的值。當請求傳送到伺服器後伺服器會通過這個屬性來和資源的最後一次的修改時間來進行比較,以此來判斷資源是否做了修改。如果資源沒有修改,那麼返回 304 狀態,讓客戶端使用本地的快取。如果資源已經被修改了,則返回修改後的資源。使用這種方法有一個缺點,就是 Last-Modified 標註的最後修改時間只能精確到秒級,如果某些檔案在1秒鐘以內,被修改多次的話,那麼檔案已將改變了但是 Last-Modified 卻沒有改變,這樣會造成快取命中的不準確。

(2)因為 Last-Modified 的這種可能發生的不準確性,http 中提供了另外一種方式,那就是 Etag 屬性。伺服器在返回資源的時候,在頭資訊中添加了 Etag 屬性,這個屬性是資源生成的唯一識別符號,當資源發生改變的時候,這個值也會發生改變。在下一次資源請求時,瀏覽器會在請求頭中新增一個 If-None-Match 屬性,這個屬性的值就是上次返回的資源的 Etag 的值。服務接收到請求後會根據這個值來和資源當前的 Etag 的值來進行比較,以此來判斷資源是否發生改變,是否需要返回資源。通過這種方式,比 Last-Modified 的方式更加精確。

當 Last-Modified 和 Etag 屬性同時出現的時候,Etag 的優先順序更高。使用協商快取的時候,伺服器需要考慮負載平衡的問題,因此多個伺服器上資源的 Last-Modified 應該保持一致,因為每個伺服器上 Etag 的值都不一樣,因此在考慮負載平衡時,最好不要設定 Etag 屬性。

總結:

強快取策略和協商快取策略在快取命中時都會直接使用本地的快取副本,區別只在於協商快取會向伺服器傳送一次請求。它們快取不命中時,都會向伺服器傳送請求來獲取資源。在實際的快取機制中,強快取策略和協商快取策略是一起合作使用的。瀏覽器首先會根據請求的資訊判斷,強快取是否命中,如果命中則直接使用資源。如果不命中則根據頭資訊向伺服器發起請求,使用協商快取,如果協商快取命中的話,則伺服器不返回資源,瀏覽器直接使用本地資源的副本,如果協商快取不命中,則瀏覽器返回最新的資源給瀏覽器。

3. 為什麼需要瀏覽器快取?

瀏覽器快取指的是瀏覽器將使用者請求過的靜態資源,儲存到本地,當瀏覽器再次訪問時,就可以直接從本地載入,不需要再去服務端請求了。使用瀏覽器快取,有以下優點:

  • 減少了伺服器的負擔,提高了網站的效能
  • 加快了客戶端網頁的載入速度
  • 減少了多餘網路資料傳輸

4. 點選重新整理按鈕或者按 F5、按 Ctrl+F5 (強制重新整理)、位址列回車有什麼區別?

  • 點選重新整理按鈕或者按 F5: 瀏覽器直接對本地的快取檔案過期,但是會帶上If-Modifed-Since,If-None-Match,這就意味著伺服器會對檔案檢查新鮮度,返回結果可能是 304,也有可能是 200。
  • 使用者按 Ctrl+F5(強制重新整理): 瀏覽器不僅會對本地檔案過期,而且不會帶上 If-Modifed-Since,If-None-Match,相當於之前從來沒有請求過,返回結果是 200。
  • 位址列回車: 瀏覽器發起請求,按照正常流程,本地檢查是否過期,然後伺服器檢查新鮮度,最後返回內容。

三、瀏覽器渲染原理

1. 瀏覽器的渲染過程

  • 首先解析收到的文件,根據文件定義構建一棵 DOM 樹
  • 然後對 CSS 進行解析,生成 CSSOM 規則樹
  • 根據 DOM 樹和 CSSOM 規則樹構建渲染樹。渲染樹的節點被稱為渲染物件,渲染物件是一個包含有顏色和大小等屬性的矩形,渲染物件和 DOM 元素相對應,但這種對應關係不是一對一的,不可見的 DOM 元素不會被插入渲染樹。
  • 當渲染物件被建立並新增到樹中,它們並沒有位置和大小,所以當瀏覽器生成渲染樹以後,就會根據渲染樹來進行佈局。
  • 佈局階段結束後是繪製階段,遍歷渲染樹並呼叫渲染物件的 paint 方法將它們的內容顯示在螢幕上,繪製使用 UI 基礎元件。

2. 瀏覽器渲染優化

(1)針對JavaScript

js 1.儘量將JavaScript檔案放在body的最後 2.body中間儘量不要寫<script>標籤 3.使用async屬性和defer屬性來非同步引入<script>標籤

(2)針對CSS

js 1.匯入外部樣式使用link,而不用@import 2.如果css少,儘可能採用內嵌樣式,直接寫在style標籤中

(3)針對DOM樹、CSSOM樹

js 1.HTML檔案的程式碼層級儘量不要太深 2.使用語義化的標籤,來避免不標準語義化的特殊處理 3.減少CSSD程式碼的層級,因為選擇器是從右向左進行解析的

(4)減少迴流與重繪

js 1.不要使用table佈局, 一個小的改動可能會使整個table進行重新佈局 2.不要頻繁操作元素的樣式 3.將DOM的多個讀操作(或者寫操作)放在一起 4.操作DOM時,儘量在低層級的DOM節點進行操作 5.避免頻繁操作DOM,可以建立一個文件片段documentFragment,在它上面應用所有DOM操作,最後再把它新增到文件中 6.使用absolute或者fixed,使元素脫離文件流,這樣他們發生變化就不會影響其他元素

3. 渲染過程中遇到 JS 檔案如何處理?

JavaScript 的載入、解析與執行會阻塞文件的解析,在構建 DOM 時,HTML 解析器若遇到了 JavaScript,那麼它會暫停文件的解析,將控制權移交給 JavaScript 引擎,等 JavaScript 引擎執行完畢,瀏覽器再從中斷的地方恢復繼續解析文件。

4. 什麼是文件的預解析?

當執行 JavaScript 指令碼時,另一個執行緒解析剩下的文件,並載入後面需要通過網路載入的資源。這種方式可以使資源並行載入從而使整體速度更快。需要注意的是,預解析並不改變 DOM 樹,它將這個工作留給主解析過程,自己只解析外部資源的引用,比如外部指令碼、樣式表及圖片。

5. CSS 如何阻塞文件解析?

理論上,既然樣式表不改變 DOM 樹,也就沒有必要停下文件的解析等待它們。然而,JavaScript 指令碼執行時可能在文件的解析過程中請求樣式資訊,如果樣式還沒有載入和解析,指令碼將得到錯誤的值,顯然這將會導致很多問題。所以如果瀏覽器尚未完成 CSSOM 的下載和構建,而我們卻想在此時執行指令碼,那麼瀏覽器將延遲 JavaScript 指令碼執行和文件的解析,直至其完成 CSSOM 的下載和構建。

6. 如何優化關鍵渲染路徑?

為儘快完成首次渲染,我們需要最大限度減小以下三種可變因素:

(1)關鍵資源的數量。

(2)關鍵路徑長度。

(3)關鍵位元組的數量。

關鍵資源是可能阻止網頁首次渲染的資源。這些資源越少,瀏覽器的工作量就越小,對 CPU 以及其他資源的佔用也就越少。同樣,關鍵路徑長度受所有關鍵資源與其位元組大小之間依賴關係圖的影響:某些資源只能在上一資源處理完畢之後才能開始下載,並且資源越大,下載所需的往返次數就越多。最後,瀏覽器需要下載的關鍵位元組越少,處理內容並讓其出現在螢幕上的速度就越快。要減少位元組數,我們可以減少資源數(將它們刪除或設為非關鍵資源),此外還要壓縮和優化各項資源,確保最大限度減小傳送大小。

優化關鍵渲染路徑的常規步驟如下:

(1)對關鍵路徑進行分析和特性描述:資源數、位元組數、長度

(2)最大限度減少關鍵資源的數量:刪除它們,延遲它們的下載

(3)優化關鍵位元組數以縮短下載時間

四、瀏覽器本地儲存

1. 瀏覽器本地儲存方式

(1)Cookie: 最早被提出來的本地儲存方式,在此之前,服務端是無法判斷網路中的兩個請求是否是同一使用者發起的。Cookie的大小隻有4kb,它是一種純文字檔案,每次發起HTTP請求都會攜帶Cookie。

```js 特性: 1.Cookie一旦建立成功,名稱就無法修改 2.Cookie是無法跨域名的,也就是說a域名和b域名下的cookie是無法共享的 3.每個域名下Cookie的數量不能超過20個,每個Cookie的大小不能超過4kb 4.有安全問題,如果Cookie被攔截了,那就可獲得session的所有資訊 5.Cookie在請求一個新的頁面的時候都會被髮送過去

使用場景:

1.我們將sessionId儲存到Cookie中,每次發請求都會攜帶這個sessionId 2.可以用來統計頁面的點選次數 ```

(2)LocalStorage: HTML5新引入的特性,由於有的時候我們儲存的資訊較大,Cookie就不能滿足我們的需求,這時候LocalStorage就派上用場了。

```js 優點: 1.LocalStorage的大小一般為5MB,可以儲存更多的資訊 2.LocalStorage是持久儲存,並不會隨著頁面的關閉而消失,除非主動清理,不然會永久存在 3.僅儲存在本地,不像Cookie那樣每次HTTP請求都會被攜帶

缺點: 1.存在瀏覽器相容問題,IE8以下版本的瀏覽器不支援 2.如果瀏覽器設定為隱私模式,那我們將無法讀取到LocalStorage 3.LocalStorage受到同源策略的限制,即埠、協議、主機地址有任何一個不相同,都不會訪問

常用API: // 儲存資料到 localStorage localStorage.setItem('key', 'value');

// 從 localStorage 獲取資料 let data = localStorage.getItem('key');

// 從 localStorage 刪除儲存的資料 localStorage.removeItem('key');

// 從 localStorage 刪除所有儲存的資料 localStorage.clear();

// 獲取某個索引的Key localStorage.key(index) ```

(3)SessionStorage: 是在HTML5才提出來的儲存方案,SessionStorage 主要用於臨時儲存同一視窗(或標籤頁)的資料,重新整理頁面時不會刪除,關閉視窗或標籤頁之後將會刪除這些資料。

```js 使用場景: 由於SessionStorage具有時效性,所以可以用來儲存一些網站的遊客登入的資訊, 還有臨時的瀏覽記錄的資訊。當關閉網站之後,這些資訊也就隨之消除了。

常用API: // 儲存資料到 sessionStorage sessionStorage.setItem('key', 'value');

// 從 sessionStorage 獲取資料 let data = sessionStorage.getItem('key');

// 從 sessionStorage 刪除儲存的資料 sessionStorage.removeItem('key');

// 從 sessionStorage 刪除所有儲存的資料 sessionStorage.clear();

// 獲取某個索引的Key sessionStorage.key(index) ```

2. Cookie有哪些欄位,作用分別是什麼

  • Value:值
  • Size: 大小
  • Name:名稱
  • Path:可以訪問此 cookie 的頁面路徑。 比如 domain 是 abc.com,path 是/test,那麼只有/test路徑下的頁面可以讀取此cookie。
  • Secure: 指定是否使用HTTPS安全協議傳送Cookie。
  • Domain:可以訪問該 cookie 的域名
  • HTTP: 該欄位包含 HTTPOnly 屬性 ,該屬性用來設定cookie能否通過指令碼來訪問。
  • Expires/Max-size:超時時間。若設定其值為一個時間,那麼當到達此時間後,此cookie失效。不設定的話預設值是Session,意思是cookie會和session一起失效。當瀏覽器關閉(不是瀏覽器標籤頁,而是整個瀏覽器) 後,此cookie失效。

3. Cookie、LocalStorage、SessionStorage區別

  • cookie: 其實最開始是伺服器端用於記錄使用者狀態的一種方式,由伺服器設定,在客戶端儲存,然後每次發起同源請求時,傳送給伺服器端。cookie 最多能儲存 4 k 資料,它的生存時間由 expires 屬性指定,並且 cookie 只能被同源的頁面訪問共享。
  • sessionStorage: html5 提供的一種瀏覽器本地儲存的方法,它借鑑了伺服器端 session 的概念,代表的是一次會話中所儲存的資料。它一般能夠儲存 5M 的資料,它在當前視窗關閉後就失效了,並且 sessionStorage 只能被同一個視窗的同源頁面所訪問共享。
  • localStorage: html5 提供的一種瀏覽器本地儲存的方法,它一般也能夠儲存 5M 的資料。它和 sessionStorage 不同的是,除非手動刪除它,否則它不會失效,並且 localStorage 也只能被同源頁面所訪問共享。

上面幾種方式都是儲存少量資料的時候的儲存方式,當需要在本地儲存大量資料的時候,我們可以使用瀏覽器的 indexDB 這是瀏覽器提供的一種本地的資料庫儲存機制。

4. 前端儲存的方式有哪些?

js cookies localStorage sessionStorage IndexedDB

5. IndexedDB有哪些特點?

  • 鍵值對儲存:IndexedDB 內部採用物件倉庫(object store)存放資料。所有型別的資料都可以直接存入,包括 JavaScript 物件。物件倉庫中,資料以"鍵值對"的形式儲存,每一個數據記錄都有對應的主鍵,主鍵是獨一無二的,不能有重複,否則會丟擲一個錯誤。
  • 非同步:IndexedDB 操作時不會鎖死瀏覽器,使用者依然可以進行其他操作,這與 LocalStorage 形成對比,後者的操作是同步的。非同步設計是為了防止大量資料的讀寫,拖慢網頁的表現。
  • 支援事務:IndexedDB 支援事務(transaction),這意味著一系列操作步驟之中,只要有一步失敗,整個事務就都取消,資料庫回滾到事務發生之前的狀態,不存在只改寫一部分資料的情況。
  • 同源限制: IndexedDB 受到同源限制,每一個數據庫對應建立它的域名。網頁只能訪問自身域名下的資料庫,而不能訪問跨域的資料庫。
  • 儲存空間大:IndexedDB 的儲存空間比 LocalStorage 大得多,一般來說不少於 250MB,甚至沒有上限。
  • 支援二進位制儲存:IndexedDB 不僅可以儲存字串,還可以儲存二進位制資料

6. localstorage會遇到什麼安全問題

js 1.採用明文儲存,所以在儲存的時候最好在伺服器端進行加密 2.可能會被植入廣告追蹤標誌 3.使用者定期清除瀏覽器快取有助於 cookie 更有效地發揮作用,但是會丟失localstorage中儲存的資料

7. token為什麼要設定過期時間,時限多長合適?

安全性問題,如果不設定過期時間,那麼token 容易被盜,就會產生不可估量的影響。比如支付寶因為涉及強資金安全性,如果時長設定過長可能導致使用者本人離開後由他人操作故意導致噁心資金流轉的問題等。

那麼token 時限多長合適呢?這就需要根據業務性來區分,涉及支付類,對資料賬號安全特別敏感的,建議每次都重新登入;對於日常使用的,比如學習類APP,工具類APP,不涉及到金額,token 時限可以長點。

8. cookie和localStorage區別

(1)儲存量的區別

cookie 單個的最大儲存為 4k,如果大於 4k,則儲存失敗,瀏覽器中找不到對應的 cookie 資訊

localStorage 的最大儲存為 5m。如果大於這個最大限制瀏覽器提示出錯

(2)儲存量的區別

cookie預設是會話級儲存,可以設定過期時間

localStorage 是持久化儲存,除非主動 clear 掉

(3)可操作

cookie 不僅僅是儲存資料,還有其他多個屬性可供其操作設定:Domain 與 Path 一起決定了 cookie 的作用範圍,Expires/Max-Age 決定了過期時間,HttpOnly 如果設為 true,那麼通過js(document.cookie)無法讀取 cookie 資料,Secure 如果設為 true,那麼 cookie 只能用 https 協議傳送給伺服器。

localStorage 只是儲存資料

(4)使用場景

cookie 的使用場景一般是作為客戶端與服務端的一種資訊傳遞,當添加了 cookie,預設的同源的 cookie 資訊會自動作為請求頭的一部分被髮送到服務端

localStorage 一般僅用作客戶端的資料儲存,如儲存一個非同步請求的結果資料,然後在頁面重渲染時,可以直接讀取 storage 中的資料,減少一次請求的傳送

五、瀏覽器同源策略

1. 什麼是同源策略

跨域問題其實就是瀏覽器的同源策略造成的。同源策略是一種安全機制,同源指的是:協議埠號域名必須一致。

| URL | 是否跨域 | 原因 | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | ----------------------- | | store.company.com/dir/page.ht… | 同源 | 完全相同 | | store.company.com/dir/inner/a… | 同源 | 只有路徑不同 | | store.company.com/secure.html | 跨域 | 協議不同 | | store.company.com:81/dir/etc.htm… | 跨域 | 埠不同 ( http:// 預設埠是80) | | news.company.com/dir/other.h… | 跨域 | 主機不同 |

同源政策主要限制了三個方面:

  • 當前域下的 js 指令碼不能夠訪問其他域下的 cookie、localStorage 和 indexDB。
  • 當前域下的 js 指令碼不能夠操作訪問操作其他域下的 DOM。
  • 當前域下 ajax 無法傳送跨域請求。

同源政策的目的主要是為了保證使用者的資訊保安,它只是對 js 指令碼的一種限制,並不是對瀏覽器的限制,對於一般的 img、或者script 指令碼請求都不會有跨域的限制,這是因為這些操作都不會通過響應結果來進行可能出現安全問題的操作。

2. 如何解決跨越問題

(1)CORS

跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器,讓執行在一個 origin (domain)上的Web應用被允許訪問來自不同源伺服器上的指定的資源。當一個資源從與該資源本身所在的伺服器不同的域、協議或埠請求一個資源時,資源會發起一個跨域HTTP 請求。

CORS 是在後端伺服器做相應配置的,這裡就以express為例:

// 配置關鍵程式碼Access-Control-Allow-Origin與Access-Control-Allow-Methods app.use((req,res,next)=>{ //實驗驗證,只需要設定這一個就可以進行get請求 res.header('Access-Control-Allow-Origin', 'http://localhost:8080')//配置8080埠跨域 //res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, OPTIONS') // 允許的 http 請求的方法 // res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With') next() })

  1. Access-Control-Allow-Origin:設定請求源,就告訴了瀏覽器。如果請求我的資源的頁面是我這個響應頭裡記錄了的"源",則不要攔截此響應,允許資料通行。如上配置,由於服務端設定了res.header('Access-Control-Allow-Origin', 'http://localhost:8080'),如果請求資料的源是 http://localhost:8080則可以允許訪問返回的資料。這樣瀏覽器就不會丟擲錯誤提示,而是正確的將資料交給你。
  2. Access-Control-Allow-Methods:允許請求的方法。
  3. Access-Control-Allow-Headers:用於preflight request(預檢請求)中,列出了將會在正式請求的 Access-Control-Expose-Headers 欄位中出現的首部資訊。

(2)JSONP

jsonp 的原理就是利用<script>標籤沒有跨域限制,通過<script>標籤src屬性,傳送帶有callback引數的GET請求,服務端將介面返回資料拼湊到callback函式中,返回給瀏覽器,瀏覽器解析執行,從而前端拿到callback函式返回的資料。

1)原生JS實現:

```

```

服務端返回如下(返回時即執行全域性函式):

handleCallback({"success": true, "user": "admin"})

2)Vue axios實現:

this.$http = axios; this.$http.jsonp('http://www.domain2.com:8080/login', { params: {}, jsonp: 'handleCallback' }).then((res) => { console.log(res); })

後端node.js程式碼:

let querystring = require('querystring'); let http = require('http'); let server = http.createServer(); server.on('request', function(req, res) { var params = querystring.parse(req.url.split('?')[1]); var fn = params.callback; // jsonp返回設定 res.writeHead(200, { 'Content-Type': 'text/javascript' }); res.write(fn + '(' + JSON.stringify(params) + ')'); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');

JSONP的缺點:

  • 具有侷限性, 僅支援get方法
  • 不安全,可能會遭受XSS攻擊

(3)postMessage 跨域

HTML5 XMLHttpRequest Level 2中的 API,且是為數不多可以跨域操作的 window 屬性之一,它可用於解決以下方面的問題:

  • 頁面和其開啟的新視窗的資料傳遞
  • 多視窗之間訊息傳遞
  • 頁面與巢狀的iframe訊息傳遞
  • 上面三個場景的跨域資料傳遞

用法:postMessage(data,origin)方法接受兩個引數:

  • data: html5規範支援任意基本型別或可複製的物件,但部分瀏覽器只支援字串,所以傳參時最好用JSON.stringify()序列化。
  • origin: 協議+主機+埠號,也可以設定為"*",表示可以傳遞給任意視窗,如果要指定和當前視窗同源的話設定為"/"。

1)a.html:(domain1.com/a.html)

```

```

2)b.html:(domain2.com/b.html)

```

```

(4)nginx代理跨域

nginx代理跨域,實質和CORS跨域原理一樣,通過配置檔案設定請求響應頭Access-Control-Allow-Origin…等欄位。

1)nginx配置解決iconfont跨域,瀏覽器跨域訪問 js、css、img等 常規靜態資源被同源策略許可,但iconfont字型檔案(eot|otf|ttf|woff|svg)例外,此時可在 nginx 的靜態資源伺服器中加入以下配置。

location / { add_header Access-Control-Allow-Origin *; }

2)nginx反向代理介面跨域:通過 Nginx 配置一個代理伺服器域名與 domain1 相同,埠不同)做跳板機,反向代理訪問domain2介面,並且可以順便修改cookie中domain資訊,方便當前域cookie寫入,實現跨域訪問。

```

proxy伺服器

server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie裡域名 index index.html index.htm; # 當用webpack-dev-server等中介軟體代理介面訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啟用 add_header Access-Control-Allow-Origin http://www.domain1.com; #當前端只跨域不帶cookie時,可為* add_header Access-Control-Allow-Credentials true; } } ```

(5)nodejs 中介軟體代理跨域

node中介軟體實現跨域代理,原理大致與nginx相同,都是通過啟一個代理伺服器,實現資料的轉發。

1)非Vue框架的跨域

  • 前端程式碼:

let xhr = new XMLHttpRequest(); // 前端開關:瀏覽器是否讀寫cookie xhr.withCredentials = true; // 訪問http-proxy-middleware代理伺服器 xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true); xhr.send();

  • 中介軟體伺服器程式碼:

let express = require('express'); let proxy = require('http-proxy-middleware'); let app = express(); app.use('/', proxy({ // 代理跨域目標介面 target: 'http://www.domain2.com:8080', changeOrigin: true, // 修改響應頭資訊,實現跨域並允許帶cookie onProxyRes: function(proxyRes, req, res) { res.header('Access-Control-Allow-Origin', 'http://www.domain1.com'); res.header('Access-Control-Allow-Credentials', 'true'); }, // 修改響應資訊中的cookie域名 cookieDomainRewrite: 'www.domain1.com' // 可以為false,表示不修改 })); app.listen(3000); console.log('Proxy server is listen at port 3000...');

2)Vue 框架的跨域

module.exports = { entry: {}, module: {}, ... devServer: { historyApiFallback: true, proxy: [{ context: '/login', target: 'http://www.domain2.com:8080', // 代理跨域目標介面 changeOrigin: true, secure: false, // 當代理某些https服務報錯時用 cookieDomainRewrite: 'www.domain1.com' // 可以為false,表示不修改 }], noInfo: true } }

(6)document.domain + iframe跨域

此方案僅限主域相同,子域不同的跨域應用場景。實現原理:兩個頁面都通過js強制設定document.domain為基礎主域,就實現了同域。

1)父視窗:(domain.com/a.html)

```

```

2)子視窗:(child.domain.com/a.html)

```

```

(7)location.hash + iframe跨域

實現原理:a 欲與 b 跨域相互通訊,通過中間頁 c 來實現。 三個頁面,不同域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通訊。

具體實現:A域:a.html -> B域:b.html -> A域:c.html,a與b不同域只能通過hash值單向通訊,b與c也不同域也只能單向通訊,但c與a同域,所以c可通過parent.parent訪問a頁面所有物件。

1)a.html:(domain1.com/a.html)

```

```

2)b.html:(.domain2.com/b.html)

```

```

3)c.html:(www.domain1.com/c.html)

```

```

(8)window.name + iframe跨域

window.name屬性的獨特之處:name值在不同的頁面(甚至不同域名)載入後依舊存在,並且可以支援非常長的 name 值(2MB)。

1)a.html:(domain1.com/a.html)

let proxy = function(url, callback) { let state = 0; let iframe = document.createElement('iframe'); // 載入跨域頁面 iframe.src = url; // onload事件會觸發2次,第1次載入跨域頁,並留存資料於window.name iframe.onload = function() { if (state === 1) { // 第2次onload(同域proxy頁)成功後,讀取同域window.name中資料 callback(iframe.contentWindow.name); destoryFrame(); } else if (state === 0) { // 第1次onload(跨域頁)成功後,切換到同域代理頁面 iframe.contentWindow.location = 'http://www.domain1.com/proxy.html'; state = 1; } }; document.body.appendChild(iframe); // 獲取資料以後銷燬這個iframe,釋放記憶體;這也保證了安全(不被其他域frame js訪問) function destoryFrame() { iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } }; // 請求跨域b頁面資料 proxy('http://www.domain2.com/b.html', function(data){ alert(data); });

2)proxy.html:(domain1.com/proxy.html)

中間代理頁,與a.html同域,內容為空即可。

3)b.html:(domain2.com/b.html)

```

```

通過 iframe 的 src 屬性由外域轉向本地域,跨域資料即由iframe的window.name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操作。

(9)WebSocket協議跨域

WebSocket protocol是HTML5一種新的協議,它實現了瀏覽器與伺服器全雙工通訊,同時允許跨域通訊。 原生 WebSocket API使用起來不太方便,我們使用 Socket.io,它很好地封裝了 webSocket 介面,提供了更簡單、靈活的介面。

1)前端程式碼:

```

user input:

```

2)Nodejs socket後臺:

let http = require('http'); let socket = require('socket.io'); // 啟http服務 let server = http.createServer(function(req, res) { res.writeHead(200, { 'Content-type': 'text/html' }); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...'); // 監聽socket連線 socket.listen(server).on('connection', function(client) { // 接收資訊 client.on('message', function(msg) { client.send('hello:' + msg); console.log('data from client: ---> ' + msg); }); // 斷開處理 client.on('disconnect', function() { console.log('Client socket has closed.'); }); });

3. 正向代理和反向代理的區別

  • 正向代理:

客戶端想獲得一個伺服器的資料,但是因為種種原因無法直接獲取。於是客戶端設定了一個代理伺服器,並且指定目標伺服器,之後代理伺服器向目標伺服器轉交請求並將獲得的內容傳送給客戶端。這樣本質上起到了對真實伺服器隱藏真實客戶端的目的。

  • 反向代理:

伺服器為了能夠將工作負載分佈到多個伺服器來提高網站效能 (負載均衡),當其收到請求後,會首先根據轉發規則來確定請求應該被轉發到哪個伺服器上,然後將請求轉發到對應的真實伺服器上。這樣本質上起到了對客戶端隱藏真實伺服器的作用。 一般使用反向代理後,需要通過修改 DNS 讓域名解析到代理伺服器 IP,這時瀏覽器無法察覺到真正伺服器的存在,當然也就不需要修改配置了。

兩者區別如圖示:

正向代理和反向代理的結構是一樣的,都是 client-proxy-server 的結構,它們主要的區別就在於中間這個 proxy 是哪一方設定的。在正向代理中,proxy 是 client 設定的,用來隱藏 client;而在反向代理中,proxy 是 server 設定的,用來隱藏 server。

4. Nginx的概念及其工作原理

Nginx 是一款輕量級的 Web 伺服器,也可以用於反向代理、負載平衡和 HTTP 快取等。Nginx 使用非同步事件驅動的方法來處理請求,是一款面向效能設計的 HTTP 伺服器。

傳統的 Web 伺服器如 Apache 是 process-based 模型的,而 Nginx 是基於event-driven模型的。正是這個主要的區別帶給了 Nginx 在效能上的優勢。

Nginx 架構的最頂層是一個 master process,這個 master process 用於產生其他的 worker process,這一點和Apache 非常像,但是 Nginx 的 worker process 可以同時處理大量的HTTP請求,而每個 Apache process 只能處理一個。

5. Vue.config.js配置proxy為什麼能解決跨域

首先瀏覽器是禁止跨域的,但是服務端不禁止,在本地執行 npm run dev 等命令時實際上是用 node 運行了一個伺服器,因此 Vue 的轉發機制 proxyTable 實際上是將請求發給自己的伺服器,再由伺服器轉發給後臺伺服器,做了一層代理。Vue的proxyTable 用的是 http-proxy-middleware 中介軟體, 因此不會出現跨域問題。

6. 瀏覽器對哪些跨域是允許的

<script><img><iframe><link><a> 等標籤都可以載入跨域資源。