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

語言: 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> 等標籤都可以加載跨域資源。