[TOC]
## 什么是 XSS 攻擊?
### 概念
* XSS 攻擊指的是跨站腳本攻擊,是一種代碼注入攻擊。攻擊者通過在網站注入惡意腳本,使之在用戶的瀏覽器上運行,從而盜取用戶的信息如 cookie 等。
* XSS 的本質是因為網站沒有對惡意代碼進行過濾,與正常的代碼混合在一起了,瀏覽器沒有辦法分辨哪些腳本是可信的,從而導致了惡意代碼的執行。
### 攻擊者可以通過這種攻擊方式可以進行以下操作:
* 獲取頁面的數據,如DOM、cookie、localStorage;
* DOS攻擊,發送合理請求,占用服務器資源,從而使用戶無法訪問服務器;
* 破壞頁面結構;
* 流量劫持(將鏈接指向某網站);
#### (2)攻擊類型
XSS 可以分為存儲型、反射型和 DOM 型:
* 存儲型指的是惡意腳本會存儲在目標服務器上,當瀏覽器請求數據時,腳本從服務器傳回并執行。
* 反射型指的是攻擊者誘導用戶訪問一個帶有惡意代碼的 URL 后,服務器端接收數據后處理,然后把帶有惡意代碼的數據發送到瀏覽器端,瀏覽器端解析這段帶有 XSS 代碼的數據后當做腳本執行,最終完成 XSS 攻擊。?
* DOM 型指的通過修改頁面的 DOM 節點形成的 XSS。
### 存儲型XSS的攻擊步驟:
1. 攻擊者將惡意代碼提交到?標?站的數據庫中。
2. ?戶打開?標?站時,?站服務端將惡意代碼從數據庫取出,拼接在 HTML 中返回給瀏覽器。
3. ?戶瀏覽器接收到響應后解析執?,混在其中的惡意代碼也被執?。
4. 惡意代碼竊取?戶數據并發送到攻擊者的?站,或者冒充?戶的?為,調??標?站接?執?攻擊者指定的操作。
這種攻擊常?于帶有?戶保存數據的?站功能,如論壇發帖、商品評論、?戶私信等。
### 反射型XSS的攻擊步驟:
1. 攻擊者構造出特殊的 URL,其中包含惡意代碼。
2. ?戶打開帶有惡意代碼的 URL 時,?站服務端將惡意代碼從 URL 中取出,拼接在 HTML 中返回給瀏覽器。
3. ?戶瀏覽器接收到響應后解析執?,混在其中的惡意代碼也被執?。
4. 惡意代碼竊取?戶數據并發送到攻擊者的?站,或者冒充?戶的?為,調??標?站接?執?攻擊者指定的操作。
* **反射型 XSS 跟存儲型 XSS 的區別**是:存儲型 XSS 的惡意代碼存在數據庫?,反射型 XSS 的惡意代碼存在 URL ?。
* 反射型 XSS 漏洞常?于通過 URL 傳遞參數的功能,如?站搜索、跳轉等。 由于需要?戶主動打開惡意的 URL 才能?效,攻擊者往往會結合多種?段誘導?戶點擊。
### DOM型XSS的攻擊步驟:
1. 攻擊者構造出特殊的 URL,其中包含惡意代碼。
2. ?戶打開帶有惡意代碼的 URL。
3. ?戶瀏覽器接收到響應后解析執?,前端 JavaScript 取出 URL 中的惡意代碼并執?。
4. 惡意代碼竊取?戶數據并發送到攻擊者的?站,或者冒充?戶的?為,調??標?站接?執?攻擊者指定的操作。
* **DOM 型 XSS 跟前兩種 XSS 的區別**:DOM 型 XSS 攻擊中,取出和執?惡意代碼由瀏覽器端完成,屬于前端JavaScript ?身的安全漏洞,?其他兩種 XSS 都屬于服務端的安全漏洞。
### ****如何防御 XSS 攻擊?****
* 可以從瀏覽器的執行來進行預防,一種是使用純前端的方式,不用服務器端拼接后返回(不使用服務端渲染)。另一種是對需要插入到 HTML 中的代碼做好充分的轉義。
* 對于 DOM 型的攻擊,主要是前端腳本的不可靠而造成的,對于數據獲取渲染和字符串拼接的時候應該對可能出現的惡意代碼情況進行判斷。
* 使用 CSP ,CSP 的本質是建立一個白名單,告訴瀏覽器哪些外部資源可以加載和執行,從而防止惡意代碼的注入攻擊。
* CSP 指的是內容安全策略,它的本質是建立一個白名單,告訴瀏覽器哪些外部資源可以加載和執行。我們只需要配置規則,如何攔截由瀏覽器自己來實現。
* 通常有兩種方式來開啟 CSP,一種是設置 HTTP 首部中的 Content-Security-Policy,一種是設置 meta 標簽的方式 。
* 對一些敏感信息進行保護,比如 cookie 使用 http-only,使得腳本無法獲取。也可以使用驗證碼,避免腳本偽裝成用戶執行一些操作。
## 什么是 CSRF 攻擊?
### 概念
* CSRF 攻擊指的是**跨站請求偽造攻擊**,攻擊者誘導用戶進入一個第三方網站,然后該網站向被攻擊網站發送跨站請求。如果用戶在被攻擊網站中保存了登錄狀態,那么攻擊者就可以利用這個登錄狀態,繞過后臺的用戶驗證,冒充用戶向服務器執行一些操作。
* CSRF 攻擊的**本質是****利用 cookie 會在同源請求中攜帶發送給服務器的特點,以此來實現用戶的冒充。**
### 攻擊類型
* 常見的 CSRF 攻擊有三種:
1. GET 類型的 CSRF 攻擊,比如在網站中的一個 img 標簽里構建一個請求,當用戶打開這個網站的時候就會自動發起提交。
2. POST 類型的 CSRF 攻擊,比如構建一個表單,然后隱藏它,當用戶進入頁面時,自動提交這個表單。
3. 鏈接類型的 CSRF 攻擊,比如在 a 標簽的 href 屬性里構建一個請求,然后誘導用戶去點擊。
### 如何防御 CSRF 攻擊?
**CSRF 攻擊可以使用以下方法來防護:**
* **進行同源檢測**,服務器根據 http 請求頭中 origin 或者 referer 信息來判斷請求是否為允許訪問的站點,從而對請求進行過濾。當 origin 或者 referer 信息都不存在的時候,直接阻止請求。這種方式的缺點是有些情況下 referer 可以被偽造,同時還會把搜索引擎的鏈接也給屏蔽了。所以一般網站會允許搜索引擎的頁面請求,但是相應的頁面請求這種請求方式也可能被攻擊者給利用。(Referer 字段會告訴服務器該網頁是從哪個頁面鏈接過來的)
* **使用 CSRF Token 進行驗證**,服務器向用戶返回一個隨機數 Token ,當網站再次發起請求時,在請求參數中加入服務器端返回的 token ,然后服務器對這個 token 進行驗證。這種方法解決了使用 cookie 單一驗證方式時,可能會被冒用的問題,但是這種方法存在一個缺點就是,我們需要給網站中的所有請求都添加上這個 token,操作比較繁瑣。還有一個問題是一般不會只有一臺網站服務器,如果請求經過負載平衡轉移到了其他的服務器,但是這個服務器的 session 中沒有保留這個 token 的話,就沒有辦法驗證了。這種情況可以通過改變 token 的構建方式來解決。
* **對** **Cookie 進行****雙重驗證**,服務器在用戶訪問網站頁面時,向請求域名注入一個Cookie,內容為隨機字符串,然后當用戶再次向服務器發送請求的時候,從 cookie 中取出這個字符串,添加到 URL 參數中,然后服務器通過對 cookie 中的數據和參數中的數據進行比較,來進行驗證。使用這種方式是利用了攻擊者只能利用 cookie,但是不能訪問獲取 cookie 的特點。并且這種方法比 CSRF Token 的方法更加方便,并且不涉及到分布式訪問的問題。這種方法的缺點是如果網站存在 XSS 漏洞的,那么這種方式會失效。同時這種方式不能做到子域名的隔離。
* **在設置 cookie 屬性的時候設置 Samesite ,限制 cookie 不能作為被第三方使用**,從而可以避免被攻擊者利用。Samesite 一共有兩種模式,一種是嚴格模式,在嚴格模式下 cookie 在任何情況下都不可能作為第三方 Cookie 使用,在寬松模式下,cookie 可以被請求是 GET 請求,且會發生頁面跳轉的請求所使用。
### 其他如:網絡劫持有哪幾種,如何防范?
?絡劫持分為兩種:
1. **DNS****劫持**: (輸?京東被強制跳轉到淘寶這就屬于dns劫持)
* DNS強制解析: 通過修改運營商的本地DNS記錄,來引導?戶流量到緩存服務器
* 302跳轉的?式: 通過監控?絡出?的流量,分析判斷哪些內容是可以進?劫持處理的,再對劫持的內存發起302跳轉的回復,引導?戶獲取內容
2. **HTTP****劫持**: (訪問?歌但是?直有貪玩藍?的?告),由于http明?傳輸,運營商會修改你的http響應內容(即加?告)
* DNS劫持由于涉嫌違法,已經被監管起來,現在很少會有DNS劫持,?http劫持依然?常盛?,最有效的辦法就是全站HTTPS,將HTTP加密,這使得運營商?法獲取明?,就?法劫持你的響應內容。
## 瀏覽器緩存
### 對瀏覽器的緩存機制的理解
**瀏覽器緩存的全過程:**
* 瀏覽器第一次加載資源,服務器返回 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;

很多網站的資源后面都加了版本號,這樣做的目的是:每次升級了 JS 或 CSS 文件后,為了防止瀏覽器進行緩存,強制改變版本號,客戶端瀏覽器就會重新下載新的 JS 或 CSS 文件 ,以保證用戶能夠及時獲得網站的最新更新。
### 瀏覽器資源緩存的位置有哪些?
* 資源緩存的位置一共有 3 種,按優先級從高到低分別是:
1. **Service Worker:**Service Worker 運行在 JavaScript 主線程之外,雖然由于脫離了瀏覽器窗體無法直接訪問 DOM,但是它可以完成離線緩存、消息推送、網絡代理等功能。它可以讓我們**自由控制**緩存哪些文件、如何匹配緩存、如何讀取緩存,并且**緩存是持續性的**。當 Service Worker 沒有命中緩存的時候,需要去調用 `fetch` 函數獲取 數據。也就是說,如果沒有在 Service Worker 命中緩存,會根據緩存查找優先級去查找數據。**但是不管是從 Memory Cache 中還是從網絡請求中獲取的數據,瀏覽器都會顯示是從 Service Worker 中獲取的內容。**
2. **Memory Cache:** Memory Cache 就是內存緩存,它的效率最快,**但是內存緩存雖然讀取高效,可是緩存持續性很短,會隨著進程的釋放而釋放。** 一旦我們關閉 Tab 頁面,內存中的緩存也就被釋放了。
3. **Disk Cache:** Disk Cache 也就是存儲在硬盤中的緩存,讀取速度慢點,但是什么都能存儲到磁盤中,比之 Memory Cache **勝在容量和存儲時效性上。** 在所有瀏覽器緩存中,Disk Cache 覆蓋面基本是最大的。它會根據 HTTP Herder 中的字段判斷哪些資源需要緩存,哪些資源可以不請求直接使用,哪些資源已經過期需要重新請求。**并且即使在跨站點的情況下,相同地址的資源一旦被硬盤緩存下來,就不會再次去請求數據。**
**Disk Cache:** Push Cache 是 HTTP/2 中的內容,當以上三種緩存都沒有命中時,它才會被使用。**并且緩存時間也很短暫,只在會話(Session)中存在,一旦會話結束就被釋放。** 其具有以下特點:
* 所有的資源都能被推送,但是 Edge 和 Safari 瀏覽器兼容性不怎么好
* 可以推送 `no-cache` 和 `no-store` 的資源
* 一旦連接被關閉,Push Cache 就被釋放
* 多個頁面可以使用相同的 HTTP/2 連接,也就是說能使用同樣的緩存
* Push Cache 中的緩存只能被使用一次
* 瀏覽器可以拒絕接受已經存在的資源推送
* 可以給其他域名推送資源
### 協商緩存和強緩存的區別
**強緩存**
* 使用強緩存策略時,如果緩存資源有效,則直接使用緩存資源,不必再向服務器發起請求。
* 強緩存策略可以通過兩種方式來設置,分別是 http 頭信息中的 Expires 屬性和 Cache-Control 屬性。
1. 服務器通過在響應頭中添加 Expires 屬性,來指定資源的過期時間。在過期時間以內,該資源可以被緩存使用,不必再向服務器發送請求。這個時間是一個絕對時間,它是服務器的時間,因此可能存在這樣的問題,就是客戶端的時間和服務器端的時間不一致,或者用戶可以對客戶端時間進行修改的情況,這樣就可能會影響緩存命中的結果。
2. Expires 是 http1.0 中的方式,因為它的一些缺點,在 HTTP 1.1 中提出了一個新的頭部屬性就是 Cache-Control 屬性,它提供了對資源的緩存的更精確的控制。它有很多不同的值,`Cache-Control`可設置的字段:
* `public`:設置了該字段值的資源表示可以被任何對象(包括:發送請求的客戶端、代理服務器等等)緩存。這個字段值不常用,一般還是使用max-age=來精確控制;
* `private`:設置了該字段值的資源只能被用戶瀏覽器緩存,不允許任何代理服務器緩存。在實際開發當中,對于一些含有用戶信息的HTML,通常都要設置這個字段值,避免代理服務器(CDN)緩存;
* `no-cache`:設置了該字段需要先和服務端確認返回的資源是否發生了變化,如果資源未發生變化,則直接使用緩存好的資源;
* `no-store`:設置了該字段表示禁止任何緩存,每次都會向服務端發起新的請求,拉取最新的資源;
* `max-age=`:設置緩存的最大有效期,單位為秒;
* `s-maxage=`:優先級高于max-age=,僅適用于共享緩存(CDN),優先級高于max-age或者Expires頭;
* `max-stale[=]`:設置了該字段表明客戶端愿意接收已經過期的資源,但是不能超過給定的時間限制。
* 一般來說只需要設置其中一種方式就可以實現強緩存策略,當兩種方式一起使用時,Cache-Control 的優先級要高于 Expires。
**no-cache和no-store很容易混淆:**
* no-cache 是指先要和服務器確認是否有資源更新,在進行判斷。也就是說沒有強緩存,但是會有協商緩存;
* no-store 是指不使用任何緩存,每次請求都直接從服務器獲取資源。
**協商緩存**
* 如果命中強制緩存,我們無需發起新的請求,直接使用緩存內容,如果沒有命中強制緩存,如果設置了協商緩存,這個時候協商緩存就會發揮作用了。
* 上面已經說到了,命中協商緩存的條件有兩個:
* `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 屬性。
**總結:**
* 強緩存策略和協商緩存策略在緩存命中時都會直接使用本地的緩存副本,區別只在于協商緩存會向服務器發送一次請求。它們緩存不命中時,都會向服務器發送請求來獲取資源。在實際的緩存機制中,強緩存策略和協商緩存策略是一起合作使用的。瀏覽器首先會根據請求的信息判斷,強緩存是否命中,如果命中則直接使用資源。如果不命中則根據頭信息向服務器發起請求,使用協商緩存,如果協商緩存命中的話,則服務器不返回資源,瀏覽器直接使用本地資源的副本,如果協商緩存不命中,則瀏覽器返回最新的資源給瀏覽器。
## 瀏覽器渲染原理
### 瀏覽器的渲染過程
瀏覽器渲染主要有以下步驟:
* 首先解析收到的文檔,根據文檔定義構建一棵 DOM 樹,DOM 樹是由 DOM 元素及屬性節點組成的。
* 然后對 CSS 進行解析,生成 CSSOM 規則樹。
* 根據 DOM 樹和 CSSOM 規則樹構建渲染樹。渲染樹的節點被稱為渲染對象,渲染對象是一個包含有顏色和大小等屬性的矩形,渲染對象和 DOM 元素相對應,但這種對應關系不是一對一的,不可見的 DOM 元素不會被插入渲染樹。還有一些 DOM元素對應幾個可見對象,它們一般是一些具有復雜結構的元素,無法用一個矩形來描述。
* 當渲染對象被創建并添加到樹中,它們并沒有位置和大小,所以當瀏覽器生成渲染樹以后,就會根據渲染樹來進行布局(也可以叫做回流)。這一階段瀏覽器要做的事情是要弄清楚各個節點在頁面中的確切位置和大小。通常這一行為也被稱為“自動重排”。
* 布局階段結束后是繪制階段,遍歷渲染樹并調用渲染對象的 paint 方法將它們的內容顯示在屏幕上,繪制使用 UI 基礎組件。
大致過程如圖所示:

**注意:** 這個過程是逐步完成的,為了更好的用戶體驗,渲染引擎將會盡可能早的將內容呈現到屏幕上,并不會等到所有的html 都解析完成之后再去構建和布局 render 樹。它是解析完一部分內容就顯示一部分內容,同時,可能還在通過網絡下載其余內容。
### 瀏覽器渲染優化
* **針對JavaScript:** JavaScript既會阻塞HTML的解析,也會阻塞CSS的解析。因此我們可以對JavaScript的加載方式進行改變,來進行優化:
1. 盡量將JavaScript文件放在body的最后
2. body中間盡量不要寫`<script>`標簽
3. `<script>`標簽的引入資源方式有三種,有一種就是我們常用的直接引入,還有兩種就是使用 async 屬性和 defer 屬性來異步引入,兩者都是去異步加載外部的JS文件,不會阻塞DOM的解析(盡量使用異步加載)。
4. 三者的區別如下:
* **script** 立即停止頁面渲染去加載資源文件,當資源加載完畢后立即執行js代碼,js代碼執行完畢后繼續渲染頁面;
* **async** 是在下載完成之后,立即異步加載,加載好后立即執行,多個帶async屬性的標簽,不能保證加載的順序;
* **defer** 是在下載完成之后,立即異步加載。加載好后,如果 DOM 樹還沒構建好,則先等 DOM 樹解析好再執行;如果DOM樹已經準備好,則立即執行。多個帶defer屬性的標簽,按照順序執行。
* **針對CSS:**使用CSS有三種方式:使用**link、@import、內聯樣式**,其中link和@import都是導入外部樣式。它們之間的區別:
* **link**:瀏覽器會派發一個新等線程(HTTP線程)去加載資源文件,與此同時GUI渲染線程會繼續向下渲染代碼
* **@import**:GUI渲染線程會暫時停止渲染,去服務器加載資源文件,資源文件沒有返回之前不會繼續渲染(阻礙瀏覽器渲染)
* **style**:GUI直接渲染
* 外部樣式如果長時間沒有加載完畢,瀏覽器為了用戶體驗,會使用瀏覽器會默認樣式,確保首次渲染的速度。所以CSS一般寫在headr中,讓瀏覽器盡快發送請求去獲取css樣式。
* 所以,在開發過程中,導入外部樣式使用link,而不用@import。如果css少,盡可能采用內嵌樣式,直接寫在style標簽中。
* **針對DOM樹、CSSOM樹:**
可以通過以下幾種方式來減少渲染的時間:
* HTML文件的代碼層級盡量不要太深
* 使用語義化的標簽,來避免不標準語義化的特殊處理
* 減少CSSD代碼的層級,因為選擇器是從左向右進行解析的
* **減少回流與重繪:**
* 操作DOM時,盡量在低層級的DOM節點進行操作
* 不要使用`table`布局, 一個小的改動可能會使整個`table`進行重新布局
* 使用CSS的表達式
* 不要頻繁操作元素的樣式,對于靜態頁面,可以修改類名,而不是樣式。
* 使用absolute或者fixed,使元素脫離文檔流,這樣他們發生變化就不會影響其他元素
* 避免頻繁操作DOM,可以創建一個文檔片段`documentFragment`,在它上面應用所有DOM操作,最后再把它添加到文檔中
* 將元素先設置`display: none`,操作結束后再把它顯示出來。因為在display屬性為none的元素上進行的DOM操作不會引發回流和重繪。
* 將DOM的多個讀操作(或者寫操作)放在一起,而不是讀寫操作穿插著寫。這得益于**瀏覽器的渲染隊列機制**。
### 渲染過程中遇到 JS 文件如何處理?
* JavaScript 的加載、解析與執行會阻塞文檔的解析,也就是說,在構建 DOM 時,HTML 解析器若遇到了 JavaScript,那么它會暫停文檔的解析,將控制權移交給 JavaScript 引擎,等 JavaScript 引擎運行完畢,瀏覽器再從中斷的地方恢復繼續解析文檔。也就是說,如果想要首屏渲染的越快,就越不應該在首屏就加載 JS 文件,這也是都建議將 script 標簽放在 body 標簽底部的原因。當然在當下,并不是說 script 標簽必須放在底部,因為你可以給 script 標簽添加 defer 或者 async 屬性。
### 什么是文檔的預解析?
* Webkit 和 Firefox 都做了這個優化,當執行 JavaScript 腳本時,另一個線程解析剩下的文檔,并加載后面需要通過網絡加載的資源。這種方式可以使資源并行加載從而使整體速度更快。需要注意的是,預解析并不改變 DOM 樹,它將這個工作留給主解析過程,自己只解析外部資源的引用,比如外部腳本、樣式表及圖片。
### CSS 如何阻塞文檔解析?
* 理論上,既然樣式表不改變 DOM 樹,也就沒有必要停下文檔的解析等待它們。然而,存在一個問題,JavaScript 腳本執行時可能在文檔的解析過程中請求樣式信息,如果樣式還沒有加載和解析,腳本將得到錯誤的值,顯然這將會導致很多問題。所以如果瀏覽器尚未完成 CSSOM 的下載和構建,而我們卻想在此時運行腳本,那么瀏覽器將延遲 JavaScript 腳本執行和文檔的解析,直至其完成 CSSOM 的下載和構建。也就是說,在這種情況下,瀏覽器會先下載和構建 CSSOM,然后再執行 JavaScript,最后再繼續文檔的解析。
## 瀏覽器本地存儲
### 1瀏覽器本地存儲
### Cookie
* Cookie是最早被提出來的本地存儲方式,在此之前,服務端是無法判斷網絡中的兩個請求是否是同一用戶發起的,為解決這個問題,Cookie就出現了。Cookie的大小只有4kb,它是一種純文本文件,每次發起HTTP請求都會攜帶Cookie。
**Cookie的特性:**
* Cookie一旦創建成功,名稱就無法修改
* Cookie是無法跨域名的,也就是說a域名和b域名下的cookie是無法共享的,這也是由Cookie的隱私安全性決定的,這樣就能夠阻止非法獲取其他網站的Cookie
* 每個域名下Cookie的數量不能超過20個,每個Cookie的大小不能超過4kb
* 有安全問題,如果Cookie被攔截了,那就可獲得session的所有信息,即使加密也于事無補,無需知道cookie的意義,只要轉發cookie就能達到目的
* Cookie在請求一個新的頁面的時候都會被發送過去
**如果需要域名之間跨域共享Cookie,有兩種方法:**
1. 使用Nginx反向代理
2. 在一個站點登陸之后,往其他網站寫Cookie。服務端的Session存儲到一個節點,Cookie存儲sessionId
**Cookie有哪些字段,作用分別是什么**
* **Name**:cookie的名稱
* **Value**:cookie的值,對于認證cookie,value值包括web服務器所提供的訪問令牌;
* **Size**: cookie的大小
* **Path**:可以訪問此cookie的頁面路徑。 比如domain是abc.com,path是`/test`,那么只有`/test`路徑下的頁面可以讀取此cookie。
* **Secure**: 指定是否使用HTTPS安全協議發送Cookie。使用HTTPS安全協議,可以保護Cookie在瀏覽器和Web服務器間的傳輸過程中不被竊取和篡改。該方法也可用于Web站點的身份鑒別,即在HTTPS的連接建立階段,瀏覽器會檢查Web網站的SSL證書的有效性。但是基于兼容性的原因(比如有些網站使用自簽署的證書)在檢測到SSL證書無效時,瀏覽器并不會立即終止用戶的連接請求,而是顯示安全風險信息,用戶仍可以選擇繼續訪問該站點。
* **Domain**:可以訪問該cookie的域名,Cookie 機制并未遵循嚴格的同源策略,允許一個子域可以設置或獲取其父域的 Cookie。當需要實現單點登錄方案時,Cookie 的上述特性非常有用,然而也增加了 Cookie受攻擊的危險,比如攻擊者可以借此發動會話定置攻擊。因而,瀏覽器禁止在 Domain 屬性中設置.org、.com 等通用頂級域名、以及在國家及地區頂級域下注冊的二級域名,以減小攻擊發生的范圍。
* **HTTP**: 該字段包含`HTTPOnly` 屬性 ,該屬性用來設置cookie能否通過腳本來訪問,默認為空,即可以通過腳本訪問。在客戶端是不能通過js代碼去設置一個httpOnly類型的cookie的,這種類型的cookie只能通過服務端來設置。該屬性用于防止客戶端腳本通過`document.cookie`屬性訪問Cookie,有助于保護Cookie不被跨站腳本攻擊竊取或篡改。但是,HTTPOnly的應用仍存在局限性,一些瀏覽器可以阻止客戶端腳本對Cookie的讀操作,但允許寫操作;此外大多數瀏覽器仍允許通過XMLHTTP對象讀取HTTP響應中的Set-Cookie頭。
* **Expires/Max-size** : 此cookie的超時時間。若設置其值為一個時間,那么當到達此時間后,此cookie失效。不設置的話默認值是Session,意思是cookie會和session一起失效。當瀏覽器關閉(不是瀏覽器標簽頁,而是整個瀏覽器) 后,此cookie失效。
**總結:**
* 服務器端可以使用 Set-Cookie 的響應頭部來配置 cookie 信息。一條cookie 包括了5個屬性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 失效的時間,domain 是域名、path是路徑,domain 和 path 一起限制了 cookie 能夠被哪些 url 訪問。secure 規定了 cookie 只能在確保安全的情況下傳輸,HttpOnly 規定了這個 cookie 只能被服務器訪問,不能使用 js 腳本訪問。
### LocalStorage
* LocalStorage是HTML5新引入的特性,由于有的時候我們存儲的信息較大,Cookie就不能滿足我們的需求,這時候LocalStorage就派上用場了。
**LocalStorage的優點:**
* 在大小方面,LocalStorage的大小一般為5MB,可以儲存更多的信息
* LocalStorage是持久儲存,并不會隨著頁面的關閉而消失,除非主動清理,不然會永久存在
* 僅儲存在本地,不像Cookie那樣每次HTTP請求都會被攜帶
**LocalStorage的缺點:**
* 存在瀏覽器兼容問題,IE8以下版本的瀏覽器不支持
* 如果瀏覽器設置為隱私模式,那我們將無法讀取到LocalStorage
* LocalStorage受到同源策略的限制,即端口、協議、主機地址有任何一個不相同,都不會訪問。
SessionStorage和LocalStorage都是在HTML5才提出來的存儲方案,SessionStorage 主要用于臨時保存同一窗口(或標簽頁)的數據,刷新頁面時不會刪除,關閉窗口或標簽頁之后將會刪除這些數據。
**SessionStorage****與LocalStorage對比:**
* SessionStorage和LocalStorage都在**本地進行數據存儲**;
* SessionStorage也有同源策略的限制,但是SessionStorage有一條更加嚴格的限制,SessionStorage**只有在同一瀏覽器的同一窗口下才能夠共享**;
* LocalStorage和SessionStorage**都不能被爬蟲爬取**。
### . Cookie、LocalStorage、SessionStorage區別
瀏覽器端常用的存儲技術是 cookie 、localStorage 和 sessionStorage。
* **cookie:**其實最開始是服務器端用于記錄用戶狀態的一種方式,由服務器設置,在客戶端存儲,然后每次發起同源請求時,發送給服務器端。cookie 最多能存儲 4 k 數據,它的生存時間由 expires 屬性指定,并且 cookie 只能被同源的頁面訪問共享。
* **sessionStorage:**html5 提供的一種瀏覽器本地存儲的方法,它借鑒了服務器端 session 的概念,代表的是一次會話中所保存的數據。它一般能夠存儲 5M 或者更大的數據,它在當前窗口關閉后就失效了,并且 sessionStorage 只能被同一個窗口的同源頁面所訪問共享。
* **localStorage:**html5 提供的一種瀏覽器本地存儲的方法,它一般也能夠存儲 5M 或者更大的數據。它和 sessionStorage 不同的是,除非手動刪除它,否則它不會失效,并且 localStorage 也只能被同源頁面所訪問共享。
## 瀏覽器同源策略
* **什么是同源策略**
* 跨域問題其實就是瀏覽器的同源策略造成的。
* 同源策略限制了從同一個源加載的文檔或腳本如何與另一個源的資源進行交互。這是瀏覽器的一個用于隔離潛在惡意文件的重要的安全機制。同源指的是:**協議**、**端口號**、**域名**必須一致。
* **同源政策主要限制了三個方面:**
* 當前域下的 js 腳本不能夠訪問其他域下的 cookie、localStorage 和 indexDB。
* 當前域下的 js 腳本不能夠操作訪問操作其他域下的 DOM。
* 當前域下 ajax 無法發送跨域請求。
* 同源政策的目的主要是為了保證用戶的信息安全,它只是對 js 腳本的一種限制,并不是對瀏覽器的限制,對于一般的 img、或者script 腳本請求都不會有跨域的限制,這是因為這些操作都不會通過響應結果來進行可能出現安全問題的操作。
## 如何解決跨越問題
### 1、**跨域資源共享(CORS)**
* 下面是MDN對于CORS的定義:
* 跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 ?讓運行在一個 origin (domain)上的Web應用被準許訪問來自不同源服務器上的指定的資源。當一個資源從與該資源本身所在的服務器不同的域、協議或端口請求一個資源時,資源會發起一個跨域HTTP 請求。
* CORS需要瀏覽器和服務器同時支持,整個CORS過程都是瀏覽器完成的,無需用戶參與。因此實現**CORS的關鍵就是服務器,只要服務器實現了CORS請求**,就可以跨源通信了。
* **瀏覽器將CORS分為簡單請求和非簡單請求:**
* 簡單請求不會觸發CORS預檢請求。若該請求滿足以下兩個條件,就可以看作是簡單請求:
* **請求方法是以下三種方法之一:**
* HEAD
* GET
* POST
* **HTTP的頭信息不超出以下幾種字段:**
* Accept
* Accept-Language
* Content-Language
* Last-Event-ID
* Content-Type:只限于三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
若不滿足以上條件,就屬于非簡單請求了。
* **簡單請求過程:**
* 對于簡單請求,瀏覽器會直接發出CORS請求,它會在請求的頭信息中增加一個Orign字段,該字段用來說明本次請求來自哪個源(協議+端口+域名),服務器會根據這個值來決定是否同意這次請求。如果Orign指定的域名在許可范圍之內,服務器返回的響應就會多出以下信息頭:
```
Access-Control-Allow-Origin: http://api.bob.com // 和Orign一直
Access-Control-Allow-Credentials: true // 表示是否允許發送Cookie
Access-Control-Expose-Headers: FooBar // 指定返回其他字段的值
Content-Type: text/html; charset=utf-8 // 表示文檔類型
```
* 如果Orign指定的域名不在許可范圍之內,服務器會返回一個正常的HTTP回應,瀏覽器發現沒有上面的Access-Control-Allow-Origin頭部信息,就知道出錯了。這個錯誤無法通過狀態碼識別,因為返回的狀態碼可能是200。
* **在簡單請求中,在服務器內,至少需要設置字段:**`**Access-Control-Allow-Origin**`
* **非簡單請求過程**
* 非簡單請求是對服務器有特殊要求的請求,比如請求方法為DELETE或者PUT等。非簡單請求的CORS請求會在正式通信之前進行一次HTTP查詢請求,**稱為預檢請求**。
* 瀏覽器會詢問服務器,當前所在的網頁是否在服務器允許訪問的范圍內,以及可以使用哪些HTTP請求方式和頭信息字段,只有得到肯定的回復,才會進行正式的HTTP請求,否則就會報錯。
* 預檢請求使用的**請求方法是OPTIONS**,表示這個請求是來詢問的。他的頭信息中的關鍵字段是Orign,表示請求來自哪個源。除此之外,頭信息中還包括兩個字段:
* **Access-Control-Request-Method**:該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法。
* **Access-Control-Request-Headers**: 該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段。
* 服務器在收到瀏覽器的預檢請求之后,會根據頭信息的三個字段來進行判斷,如果返回的頭信息在中有Access-Control-Allow-Origin這個字段就是允許跨域請求,如果沒有,就是不同意這個預檢請求,就會報錯。
* 服務器回應的CORS的字段如下:
```
Access-Control-Allow-Origin: http://api.bob.com // 允許跨域的源地址
Access-Control-Allow-Methods: GET, POST, PUT // 服務器支持的所有跨域請求的方法
Access-Control-Allow-Headers: X-Custom-Header // 服務器支持的所有頭信息字段
Access-Control-Allow-Credentials: true // 表示是否允許發送Cookie
Access-Control-Max-Age: 1728000 // 用來指定本次預檢請求的有效期,單位為秒
```
* 只要服務器通過了預檢請求,在以后每次的CORS請求都會自帶一個Origin頭信息字段。服務器的回應,也都會有一個Access-Control-Allow-Origin頭信息字段。
* **在非簡單請求中,至少需要設置以下字段:**
```
'Access-Control-Allow-Origin'
'Access-Control-Allow-Methods'
'Access-Control-Allow-Headers'
```
* CORS中Cookie相關問題:
* 在CORS請求中,如果想要傳遞Cookie,就要滿足以下三個條件:
* 在請求中設置 withCredentials
* 默認情況下在跨域請求,瀏覽器是不帶 cookie 的。但是我們可以通過設置 withCredentials 來進行傳遞 cookie.
```
// 原生 xml 的設置方式
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// axios 設置方式
axios.defaults.withCredentials = true;
```
* **Access-Control-Allow-Credentials 設置為 true**
* **Access-Control-Allow-Origin 設置為非** `*****`
### JSONP
* **jsonp** 的原理就是利用`<script>`標簽沒有跨域限制,通過`<script>`標簽src屬性,發送帶有callback參數的GET請求,服務端將接口返回數據拼湊到callback函數中,返回給瀏覽器,瀏覽器解析執行,從而前端拿到callback函數返回的數據。
### postMessage 跨域
* postMessage是HTML5 XMLHttpRequest Level 2中的API,且是為數不多可以跨域操作的window屬性之一。
### nginx代理跨域
* nginx代理跨域,實質和CORS跨域原理一樣,通過配置文件設置請求響應頭Access-Control-Allow-Origin…等字段。
* nginx配置解決iconfont跨域
* 瀏覽器跨域訪問js、css、img等常規靜態資源被同源策略許可,但iconfont字體文件(eot|otf|ttf|woff|svg)例外,此時可在nginx的靜態資源服務器中加入以下配置。
```
location / {
add_header Access-Control-Allow-Origin *;
}
```
* nginx反向代理接口跨域
* 跨域問題:同源策略僅是針對瀏覽器的安全策略。服務器端調用HTTP接口只是使用HTTP協議,不需要同源策略,也就不存在跨域問題。
* 實現思路:通過Nginx配置一個代理服務器域名與domain1相同,端口不同)做跳板機,反向代理訪問domain2接口,并且可以順便修改cookie中domain信息,方便當前域cookie寫入,實現跨域訪問。
```
#nginx具體配置: 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;
}
}
```
### nodejs 中間件代理跨域
* node中間件實現跨域代理,原理大致與nginx相同,都是通過啟一個代理服務器,實現數據的轉發,也可以通過設置cookieDomainRewrite參數修改響應頭中cookie中域名,實現當前域的cookie寫入,方便接口登錄認證。
### location.hash + iframe跨域
* 實現原理:a欲與b跨域相互通信,通過中間頁c來實現。 三個頁面,不同域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通信。
### window.name + iframe跨域
* window.name屬性的獨特之處:name值在不同的頁面(甚至不同域名)加載后依舊存在,并且可以支持非常長的 name 值(2MB)。
### WebSocket協議跨域
* WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通信,同時允許跨域通訊,是server push技術的一種很好的實現。
* 原生WebSocket API使用起來不太方便,我們使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。
### 正向代理和反向代理的區別
* **正向代理:**
* 客戶端想獲得一個服務器的數據,但是因為種種原因無法直接獲取。于是客戶端設置了一個代理服務器,并且指定目標服務器,之后代理服務器向目標服務器轉交請求并將獲得的內容發送給客戶端。這樣本質上起到了對真實服務器隱藏真實客戶端的目的。實現正向代理需要修改客戶端,比如修改瀏覽器配置。
* **反向代理:**
* 服務器為了能夠將工作負載分不到多個服務器來提高網站性能 (負載均衡)等目的,當其受到請求后,會首先根據轉發規則來確定請求應該被轉發到哪個服務器上,然后將請求轉發到對應的真實服務器上。這樣本質上起到了對客戶端隱藏真實服務器的作用。
一般使用反向代理后,需要通過修改 DNS 讓域名解析到代理服務器 IP,這時瀏覽器無法察覺到真正服務器的存在,當然也就不需要修改配置了。
* 兩者區別如圖示:

* 正向代理和反向代理的結構是一樣的,都是 client-proxy-server 的結構,它們主要的區別就在于中間這個 proxy 是哪一方設置的。在正向代理中,proxy 是 client 設置的,用來隱藏 client;而在反向代理中,proxy 是 server 設置的,用來隱藏 server。
### 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 只能處理一個。
## 瀏覽器事件機制
### 對事件委托的理解
* 事件委托本質上是利用了**瀏覽器事件冒泡**的機制。因為事件在冒泡過程中會上傳到父節點,父節點可以通過事件對象獲取到目標節點,因此可以把子節點的監聽函數定義在父節點上,由父節點的監聽函數統一處理多個子元素的事件,這種方式稱為事件委托(事件代理)。
* 使用事件委托可以不必要為每一個子元素都綁定一個監聽事件,這樣減少了內存上的消耗。并且使用事件代理還可以實現事件的動態綁定,比如說新增了一個子節點,并不需要單獨地為它添加一個監聽事件,它綁定的事件會交給父元素中的監聽函數來處理。
### 同步和異步的區別
* **同步**指的是當一個進程在執行某個請求時,如果這個請求需要等待一段時間才能返回,那么這個進程會一直等待下去,直到消息返回為止再繼續向下執行。
* **異步**指的是當一個進程在執行某個請求時,如果這個請求需要等待一段時間才能返回,這個時候進程會繼續往下執行,不會阻塞等待消息的返回,當消息返回時系統再通知進程進行處理。
### 對事件循環的理解
* 因為 js 是單線程運行的,在代碼執行時,通過將不同函數的執行上下文壓入執行棧中來保證代碼的有序執行。在執行同步代碼時,如果遇到異步事件,js 引擎并不會一直等待其返回結果,而是會將這個事件掛起,繼續執行執行棧中的其他任務。當異步事件執行完畢后,再將異步事件對應的回調加入到一個任務隊列中等待執行。任務隊列可以分為宏任務隊列和微任務隊列,當當前執行棧中的事件執行完畢后,js 引擎首先會判斷微任務隊列中是否有任務可以執行,如果有就將微任務隊首的事件壓入棧中執行。當微任務隊列中的任務都執行完成后再去執行宏任務隊列中的任務。

* Event Loop 執行順序如下所示:
* 首先執行同步代碼,這屬于宏任務
* 當執行完所有同步代碼后,執行棧為空,查詢是否有異步代碼需要執行
* 執行所有微任務
* 當執行完所有微任務后,如有必要會渲染頁面
* 然后開始下一輪 Event Loop,執行宏任務中的異步代碼
### 宏任務和微任務分別有哪些
* 微任務包括: promise 的回調、node 中的 process.nextTick 、對 Dom 變化監聽的 MutationObserver。
* 宏任務包括: script 腳本的執行、setTimeout ,setInterval ,setImmediate 一類的定時事件,還有如 I/O 操作、UI 渲染等。
## 瀏覽器垃圾回收機制
### V8的垃圾回收機制是怎樣的
V8 實現了準確式 GC,GC 算法采用了分代式垃圾回收機制。因此,V8 將內存(堆)分為新生代和老生代兩部分。
* **新生代算法**
* 新生代中的對象一般存活時間較短,使用 Scavenge GC 算法。
* 在新生代空間中,內存空間分為兩部分,分別為 From 空間和 To 空間。在這兩個空間中,必定有一個空間是使用的,另一個空間是空閑的。新分配的對象會被放入 From 空間中,當 From 空間被占滿時,新生代 GC 就會啟動了。算法會檢查 From 空間中存活的對象并復制到 To 空間中,如果有失活的對象就會銷毀。當復制完成后將 From 空間和 To 空間互換,這樣 GC 就結束了。
**老生代算法**
* 老生代中的對象一般存活時間較長且數量也多,使用了兩個算法,分別是標記清除算法和標記壓縮算法。
* 先來說下什么情況下對象會出現在老生代空間中:
* 新生代中的對象是否已經經歷過一次 Scavenge 算法,如果經歷過的話,會將對象從新生代空間移到老生代空間中。
* To 空間的對象占比大小超過 25 %。在這種情況下,為了不影響到內存分配,會將對象從新生代空間移到老生代空間中。
### 哪些操作會造成內存泄漏?
* 第一種情況是由于使用未聲明的變量,而意外的創建了一個全局變量,而使這個變量一直留在內存中無法被回收。
* 第二種情況是設置了 setInterval 定時器,而忘記取消它,如果循環函數有對外部變量的引用的話,那么這個變量會被一直留在內存中,而無法被回收。
* 第三種情況是獲取一個 DOM 元素的引用,而后面這個元素被刪除,由于我們一直保留了對這個元素的引用,所以它也無法被回收。
* 第四種情況是不合理的使用閉包,從而導致某些變量一直被留在內存當中。
- 首頁
- 2021年
- 基礎知識
- 同源策略
- 跨域
- css
- less
- scss
- reset
- 超出文本顯示省略號
- 默認滾動條
- 清除浮動
- line-height與vertical-align
- box-sizing
- 動畫
- 布局
- JavaScript
- 設計模式
- 深淺拷貝
- 排序
- canvas
- 防抖節流
- 獲取屏幕/可視區域寬高
- 正則
- 重繪重排
- rem換算
- 手寫算法
- apply、call和bind原理與實現
- this的理解-普通函數、箭頭函數
- node
- nodejs
- express
- koa
- egg
- 基于nodeJS的全棧項目
- 小程序
- 常見問題
- ec-canvas之橫豎屏切換重繪
- 公眾號后臺基本配置
- 小程序發布協議更新
- 小程序引入iconfont字體
- Uni-app
- 環境搭建
- 項目搭建
- 數據庫
- MySQL數據庫安裝
- 數據庫圖形化界面常用命令行
- cmd命令行操作數據庫
- Redis安裝
- APP
- 控制縮放meta
- GIT
- 常用命令
- vsCode
- 常用插件
- Ajax
- axios-services
- 文章
- 如何讓代碼更加優雅
- 虛擬滾動
- 網站收藏
- 防抖節流之定時器清除問題
- 號稱破解全網會員的腳本
- 資料筆記
- 資料筆記2
- 公司面試題
- 服務器相關
- 前端自動化部署-jenkins
- nginx.conf配置
- https添加證書
- shell基本命令
- 微型ssh-deploy前端部署插件
- webpack
- 深入理解loader
- 深入理解plugin
- webpack注意事項
- vite和webpack區別
- React
- react+antd搭建
- Vue
- vue-cli
- vue.config.js
- 面板分割左右拖動
- vvmily-admin-template
- v-if與v-for那個優先級高?
- 下載excel
- 導入excel
- Echart-China-Map
- vue-xlsx(解析excel)
- 給elementUI的el-table添加骨架
- cdn引入配置
- Vue2.x之defineProperty應用
- 徹底弄懂diff算法的key作用
- 復制模板內容
- 表格操作按鈕太多
- element常用組件二次封裝
- Vue3.x
- Vue3快速上手(第一天)
- Vue3.x快速上手(第二天)
- Vue3.x快速上手(第三天)
- vue3+element-plus搭建項目
- vue3
- 腳手架
- vvmily-cli
- TS
- ts筆記
- common
- Date
- utils
- axios封裝
- 2022年
- HTML
- CSS基礎
- JavaScript 基礎
- 前端框架Vue
- 計算機網絡
- 瀏覽器相關
- 性能優化
- js手寫代碼
- 前端安全
- 前端算法
- 前端構建與編譯
- 操作系統
- Node.js
- 一些開放問題、智力題