[TOC]
# 從輸入URL到頁面加載完成的整個過程
*****
1. 首先做 DNS 查詢,如果這一步做了智能 DNS 解析的話,會提供訪問速度最快的 IP 地址回來
2. 接下來是 TCP 握手,應用層會下發數據給傳輸層,這里 TCP 協議會指明兩端的端口號,然后下發給網絡層。網絡層中的 IP 協議會確定 IP 地址,并且指示了數據傳輸中如何跳轉路由器。然后包會再被封裝到數據鏈路層的數據幀結構中,最后就是物理層面的傳輸了
3. TCP 握手結束后會進行 TLS 握手,然后就開始正式的傳輸數據(如果使用HTTPS)
4. 數據在進入服務端之前,可能還會先經過負責負載均衡的服務器,它的作用就是將請求合理的分發到多臺服務器上,這時假設服務端會響應一個 HTML 文件
5. 首先瀏覽器會判斷狀態碼是什么,如果是 200 那就繼續解析,如果 400 或 500 的話就會報錯,如果 300 的話會進行重定向,這里會有個重定向計數器,避免過多次的重定向,超過次數也會報錯
6. 瀏覽器開始解析文件,如果是 gzip 格式的話會先解壓一下,然后通過文件的編碼格式知道該如何去解碼文件
7. 文件解碼成功后會正式開始渲染流程,先會根據 HTML 構建 DOM 樹,有 CSS 的話會去構建 CSSOM 樹。如果遇到`script`標簽的話,會判斷是否存在`async`或者`defer`,前者會并行進行下載并執行 JS,后者會先下載文件,然后等待 HTML 解析完成后順序執行,如果以上都沒有,就會阻塞住渲染流程直到 JS 執行完畢。遇到文件下載的會去下載文件,這里如果使用 HTTP 2.0 協議的話會極大的提高多圖的下載效率。
8. 初始的 HTML 被完全加載和解析后會觸發`DOMContentLoaded`事件
9. CSSOM 樹和 DOM 樹構建完成后會開始生成 Render 樹,這一步就是確定頁面元素的布局、樣式等等諸多方面的東西
10. 在生成 Render 樹的過程中,瀏覽器就開始調用 GPU 繪制,合成圖層,將內容顯示在屏幕上了
11. 沒有要傳輸的文件了,斷開TCP連接 4 次揮手
# 性能優化分析
*****
根據上面的過程可以看到,頁面的加載過程主要分為下載、解析、渲染三個步驟,整體可以從兩個角度來考慮:
- 網頁的資源請求與加載階段
- 網頁渲染階段
## 網頁的資源請求與加載階段
我們可以打開 Chrome 的調試工具來分析此階段的性能指標

在建立 TCP 連接的階段(HTTP 協議是建立在 TCP 協議之上的)
- Queuing 和 Stalled 表示請求隊列以及請求等待的時間
- DNS Lookup 表示執行 DNS 查詢所用的時間。頁面上的每一個新域都需要完整的往返才能執行DNS查詢
- Initila connection 和 SSL 包括 TCP 握手重試和協商 SSL 以及 SSL 握手的時間。
在請求響應的階段
- Request sent 是發出網絡請求所用的時間,通常不會超過 1ms
- Watiting(TTFB) 是等待初始響應所用的時間,也稱為等待返回首個字節的時間,該時間將捕捉到服務器往返的延遲時間,以及等待服務器傳送響應所用的時間。
- Content Download 則是從服務器上接收數據的時間。
## 資源請求階段優化方案
依據上面的指標給出以下幾點優化方案(僅供參考)
### 1、劃分子域
條件:擁有多個域名
Chrome 瀏覽器只允許每個源擁有 6 個 TCP 連接,因此可以通過劃分子域的方式,將多個資源分布在不同子域上用來減少請求隊列的等待時間。然而,劃分子域并不是一勞永逸的方式,多個子域意味著更多的 DNS 查詢時間。通常劃分為 3 到 5 個比較合適。
對如何拆分資源有如下建議:
- 前端類:把項目業務本身的 html、css、js、圖標等歸為一類
- 靜態類:CDN 資源
- 動態類:后端 API
### 2、DNS 預解析
DNS 解析也是需要時間的,可以通過預解析的方式來預先獲得域名所對應的 IP,方法是在 head 標簽里寫上幾個 link 標簽
```html
<link rel="dns-prefetch" href="https://www.google.com">
<link rel="dns-prefetch" href="https://www.google-analytics.com">
```
對以上幾個網站提前解析,這個過程是并行的,不會阻塞頁面渲染。
### 3、預加載
在開發中,可能會遇到這樣的情況。有些資源不需要馬上用到,但是希望盡早獲取,這時候就可以使用預加載。
預加載其實是聲明式的 fetch,強制瀏覽器請求資源,并且不會阻塞 onload 事件,可以使用以下代碼開啟預加載:
```html
<link rel="preload" href="http://example.com">
```
預加載可以一定程度上降低首屏的加載時間,因為可以將一些不影響首屏但重要的文件延后加載,唯一缺點就是兼容性不好。
### 4、保持持久連接
HTTP 是一個無狀態的面向連接的協議,即每個 HTTP 請求都是獨立的。然而無狀態并不代表 HTTP 不能保持 TCP 連接,Keep-Alive 正是 HTTP 協議中保持 TCP 連接非常重要的一個屬性。 HTTP1.1 協議中,Keep-Alive 默認打開,使得通信雙方在完成一次通信后仍然保持一定時長的連接,因此瀏覽器可以在一個單獨的連接上進行多個請求,有效地降低建立 TCP 請求所消耗的時間。
### 5、CND 加速
使用 CND 加速可以減少客戶端到服務器的網絡距離。
- CDN 的意圖就是盡可能地減少資源在轉發、傳輸、鏈路抖動等情況下順利保障信息的連貫性;
- CDN 系統能夠實時地根據網絡流量和各節點的連接、負載狀況以及到用戶的距離和響應時間等綜合信息將用戶的請求重新導向離用戶最近的服務節點上
- CDN 采用各節點緩存的機制,當我們項目的靜態資源修改后,如果 CDN 緩存沒有做相應更新,則看到的還是舊的網頁,解決的辦法是刷新緩存,七牛云、騰訊云都可單獨針對某個文件/目錄進行刷新;
- CDN 緩存需要合理地使用:圖片、常用 js 組件、css 重置樣式等,即不常改動的文件可走 CDN,包括項目內的一些介紹頁;
還有一種比較流行的做法是讓一些項目依賴走 CDN,比如 vuex、vue-router 這些插件通過外鏈的形式來引入,因為它們都有自己免費的 CDN,這樣可以減少打包后的文件體積。
### 6、設置緩存
緩存對于前端性能優化來說是個很重要的點,良好的緩存策略可以降低資源的重復加載提高網頁的整體加載速度。
通常瀏覽器緩存策略分為兩種:**強緩存** 和 **協商緩存**。
- 強緩存:實現強緩存可以通過兩種響應頭實現:`Expires`和`Cache-Control`強緩存表示在緩存期間不需要向服務器發送請求
- 協商緩存:緩存過期了就是用協商緩存,其通過`Last-Modified/If-Modified-Since`和`ETag/If-None-Match`實現
HTTP 頭中與緩存相關的屬性,主要有以下幾個:
(1) Expires: 指定緩存過期的時間,是一個絕對時間,但受客戶端和服務端時鐘和時區差異的影響,是 HTTP/1.0 的產物
形如`Expires: Wed, 22 Oct 2018 08:41:00 GMT`
(2) Cache-Control:比 Expires 策略更詳細,max-age 優先級比 Expires 高,其值可以是以下五種情況
1. no-cache: 強制所有緩存了該響應的緩存用戶,在使用已存儲的緩存數據前,發送請求到原始服務器(進行過期認證),通常情況下,過期認證需要配合 etag 和 Last-Modified 進行一個比較
2. no-store: 告訴客戶端不要響應緩存(禁止使用緩存,每一次都重新請求數據)
3. public: 緩存響應,并可以在多用戶間共享(與中間代理服務器相關)
4. private: 緩存響應,但不能在多用戶間共享(與中間代理服務器相關)
5. max-age: 緩存在指定時間(單位為秒)后過期
(3) Last-Modified / If-Modified-Since: `Last-Modified`表示本地文件最后修改日期,`If-Modified-Since`會將上次從服務器獲取的`Last-Modified`的值發送給服務器,詢問服務器在該日期后資源是否有更新,有更新的話就會將新的資源發送回來。
但是如果(服務器)在本地打開緩存文件(或者刪了個字符 a 后又填上去),就會造成`Last-Modified`被修改,所以在 HTTP / 1.1 出現了`ETag`。
(4) Etag / If-None-Match: `ETag`類似于文件指紋,`If-None-Match`會將當前`ETag`發送給服務器,詢問該資源`ETag`是否變動,有變動的話就將新的資源發送回來。并且`ETag`優先級比`Last-Modified`高。
由于 etag 要使用少數的字符表示一個不定大小的文件(如 etag: "58c4e2a1-f7"),所以 etag 是有重合的風險的,如果網站的信息特別重要,連很小的概率如百萬分之一都不允許,那么就不要使用 etag 了。使用 etag 的代價是增加了服務器的計算負擔,特別是當文件比較大時。

>TODO:硬件緩存?瀏覽器怎么緩存的?怎么匹配緩存文件的?緩存到哪?
<span style="font-size: 20px;">選擇合適的緩存策略</span>
對于大部分的場景都可以使用強緩存配合協商緩存解決,但是在一些特殊的地方可能需要選擇特殊的緩存策略
* 對于某些不需要緩存的資源,可以使用`Cache-control: no-store`,表示該資源不需要緩存
* 對于頻繁變動的資源,可以使用`Cache-Control: no-cache`并配合`ETag`使用,表示該資源已被緩存,但是每次都會發送請求詢問資源是否更新。
* 對于代碼文件來說,通常使用`Cache-Control: max-age=31536000`并配合策略緩存使用,然后對文件進行指紋處理,一旦文件名變動就會立刻下載新的文件。
### 7、使用 HTTP / 2.0
因為瀏覽器會有并發請求限制,在 HTTP / 1.1 時代,每個請求都需要建立和斷開,消耗了好幾個 RTT 時間,并且由于 TCP 慢啟動的原因,加載體積大的文件會需要更多的時間。
在 HTTP / 2.0 中引入了多路復用,能夠讓多個請求使用同一個 TCP 鏈接,極大的加快了網頁的加載速度。并且還支持 Header 壓縮,進一步的減少了請求的數據大小。
>[success]使用 Nginx 只需要設置幾行配置就可以實現上述的一些功能,見服務器部分
### 8、圖片和文件壓縮
這又涉及到很多知識點了,簡單來說,我們要盡可能地在保證我們的 App 能正常運行、圖片盡可能保證高質量的前提下去壓縮所有用到的文件的體積。比如圖片格式的選擇、去掉我們代碼中的注釋、空行、無關代碼等。
**圖片相關優化**
1. 不用圖片。很多時候會使用到很多修飾類圖片,其實這類修飾圖片完全可以用 CSS 去代替。
2. 對于移動端來說,屏幕寬度就那么點,完全沒有必要去加載原圖浪費帶寬。一般圖片都用 CDN 加載,可以計算出適配屏幕的寬度,然后去請求相應裁剪好的圖片。
3. 小圖使用 base64 格式
4. 選擇正確的圖片格式:
* 對于能夠顯示 WebP 格式的瀏覽器盡量使用 WebP 格式。因為 WebP 格式具有更好的圖像數據壓縮算法,能帶來更小的圖片體積,而且擁有肉眼識別無差異的圖像質量,缺點就是兼容性并不好
* 小圖使用 PNG,其實對于大部分圖標這類圖片,完全可以使用 SVG 代替
* 照片使用 JPEG
**構建工具的使用**
- 對于 Webpack4,打包項目使用 production 模式,這樣會自動開啟代碼壓縮
- 使用 ES6 模塊來開啟 tree shaking,這個技術可以移除沒有使用的代碼
- 優化圖片,對于小圖可以使用 base64 的方式寫入文件中
- 按照路由拆分代碼,實現按需加載
- 給打包出來的文件名添加哈希,實現瀏覽器緩存文件(能及時更新)
- 啟用 gzip 壓縮(需要前后端支持)
- 各種 loader/plugin 的使用
壓縮 HTML 文件
可以把 HTML 的注釋去掉,把行前縮進刪掉,這樣處理的文件可以明顯減少 HTML 的體積;這樣做幾乎是沒有風險的,除了 pre 標簽不能夠去掉行首縮進之外,其他的都正常。
## 網頁渲染階段優化方案
### 1、`<script>`標簽位置
渲染線程和 JS 引擎線程是互斥的,如果你想首屏渲染的越快,就越不應該在首屏就加載 JS 文件,因此建議將 script 標簽放在 body 標簽底部的原因。或者使用給 script 標簽添加 defer 或者 async 屬性 。
- defer 表示該文件會并行下載,但是會放到 HTML 解析完成后再順序執行;
- 對于沒有任何依賴的 JS 文件可以加上 async 屬性,表示加載和渲染后續文檔元素的過程將和 JS 文件的加載與執行并行無序進行
### 2、Webworker 的使用
執行 JS 代碼過長會卡住渲染,對于需要很多時間計算的代碼可以考慮使用?Webworker。Webworker 可以讓我們另開一個線程執行腳本(這并沒有改變 JS 單線程的本質,因為新開的線程受控于主線程且不得操作 DOM)而不影響渲染。
### 3、懶加載
懶加載就是將不關鍵的資源延后加載。
懶加載的原理就是只加載自定義區域(通常是可視區域,但也可以是即將進入可視區域)內需要加載的東西。對于圖片來說,先設置圖片標簽的`src`屬性為一張占位圖,將真實的圖片資源放入一個自定義屬性中,當進入自定義區域時,就將自定義屬性替換為`src`屬性,這樣圖片就會去下載資源,實現了圖片懶加載。
懶加載不僅可以用于圖片,也可以使用在別的資源上。比如進入可視區域才開始播放視頻等等。
### 4、預加載
- 圖片等靜態資源在使用之前的提前請求
- 資源使用到時能從緩存中加載,提升用戶體驗
- 頁面展示的依賴關系維護
使用場景比如抽獎動畫展示過程中預先加載其他內容,或者電子書閱讀章節的預加載可以使切換下一章節時更為流暢。
### 5、減少回流與重繪
執行 JavaScript 的解析和 UI 渲染的兩個瀏覽器線程是互斥的,UI 渲染時 JS 代碼解析終止,反之亦然。
當 **頁面布局和幾何屬性** 改變時,就會觸發 **回流**。
當 **需要更新的只是元素的某些外觀** 時,就會觸發 **重繪**。
- 用 translate 替代 top 屬性:top 會觸發 reflow,但 translate 不會
- 不要一條一條地修改 DOM 的樣式,預先定義好 class,然后修改 DOM 的 className
- 把 DOM 離線后修改,比如:先把 DOM 給 display:none(有一次 reflow),然后你修改 100 次,然后再把它顯示出來
- 不要把 DOM 節點的屬性值放在一個循環里當成循環的變量
- offsetHeight、offsetWidth 每次都要刷新緩沖區,緩沖機制被破壞,先用變量存儲下來
- 不要使用 table 布局,可能很小的一個小改動會造成整個 table 的重新布局
- 動畫實現的速度的選擇:選擇合適的動畫速度
- 啟用 gpu 硬件加速(并行運算),gpu 加速意味著數據需要從 cpu 走總線到 gpu 傳輸,需要考慮傳輸損耗.
- transform:translateZ(0)
- transform:translate3D(0)
- 似乎現在瀏覽器能智能地分析 gpu 加速了?
### 6、編寫高效率的 CSS
使用 CSS 預處理器時注意不要有過多的嵌套,嵌套層次過深會影響瀏覽器查找選擇器的速度,且一定程度上會產生出很多冗余的字節。
### 7、減少 DOM 元素數量、減少 DOM 的操作
減少 DOM 元素數量,合理利用 :after、:before 等偽類,避免頁面過深的層級嵌套;
優化 JavaScript 性能,減少 DOM 操作次數(或集中操作),能有效規避頁面重繪/重排;
只能說盡可能去做優化,如數據分頁、首屏直出、按需加載等
### 8、函數節流
為觸發頻率較高的函數使用函數節流
# 其他
## SPA SEO SSR
<span style="font-size: 20px; font-family:楷體;font-weight: 700;">SPA:單頁面富應用</span>
動態地重寫頁面的部分與用戶交互而不是加載新的頁面。
優點:① 前后端分離 ② 頁面之間切換快 ③ 后端只需提供 API
缺點:① 首屏速度慢,因為用戶首次加載 SPA 框架及應用程序的代碼然后才渲染頁面 ② 不利于 SEO
<span style="font-size: 20px; font-family:楷體;font-weight: 700;">SEO(Search Engine Optimization):搜索引擎優化</span>
常用技術:利用 \<title> 標簽和 \<meta> 標簽的 description
``` html
<html>
<head>
<title>標題內容</title>
?<meta name="description" content="描述內容">
?<meta name="keyword" content="關鍵字1,關鍵字2,—">
?</head>
</html>
```
SPA 應用中,通常通過 AJAX 獲取數據,而這里就難以保證我們的頁面能被搜索引擎正常收錄到。并且有一些搜索引擎不支持執行 JS 和通過 AJAX 獲取數據,那就更不用提 SEO 了。
對于有些網站而言,SEO 顯得至關重要,例如主要以內容輸出為主的 Quora、stackoverflow、知乎和豆瓣等等,那如何才能正常使用 SPA 而又不影響 SEO 呢 ?所以有了 SSR
<span style="font-size: 20px; font-family:楷體;font-weight: 700;">SSR(Server-Side Rendering):服務端渲染</span>
>以下內容部分參考《深入淺出 React 與 Redux》- 程墨
為了量化網頁性能,我們定義兩個指標:
* TTFP(Time To First Paint):指的是從網頁 HTTP 請求發出,到用戶可以看到第一個有意義的內容渲染出來的時間差
* TTI(Time To Interactive):指的是從網頁 HTTP 請求發出,到用戶可以對網頁內容進行交互的時間
在一個 **完全靠瀏覽器端渲染** 的應用中,當用戶在瀏覽器中打開一個頁面的時候,最壞情況下沒有任何緩存,需要等待三個 HTTP 請求才能到達 TTFP 的時間點:
* 向服務器獲取 HTML,這個 HTML 只是一個無內容的空架子,但是皮之不存毛將焉附,這個 HTML 就是皮,在其中運行的 JavaScript 就是毛,所以這個請求是不可省略的
* 獲取 JavaScript 文件,大部分情況下,如果這是瀏覽器第二次訪問這個網站,就可以直接讀取緩存,不會發出真正的 HTTP 請求
* 訪問 API 服務器獲取數據,得到的數據將由 JavaScript 加工之后用來填充 DOM 樹,如果應用的是 React,那就是通過修改組件的狀態或者屬性來驅動渲染
而對于服務器端渲染,因為獲取 HTTP 請求之后就會返回所有有內容的 HTML,所以在一個 HTTP 的周期之后就會提供給瀏覽器有意義的內容,所以首次渲染時間 TTFP 會優于完全依賴于瀏覽器端渲染的頁面。
<br/>
除了更短的 TTFP,服務器端渲染還有一個好處就是利于搜索引擎優化,雖然某些搜索引擎已經能夠索引瀏覽器端渲染的網頁,但是畢竟不是所有搜索引擎都能做到這一點,讓搜索引擎能夠索引到應用頁面的最直接方法就是提供完整 HTML
<br/>
上面的性能對比只是理論上的分析,實際上,采用服務器端渲染是否能獲得更好的 TTFP 有多方面因素。
1、服務器端產生的 HTML 過大是否會影響性能?
因為服務器端渲染返回的是完整的 HTML,那么下載這個 HTML 的時間也會增長。
2、服務器端渲染的運算消耗是否是服務器能夠承擔得起的?
瀏覽器端渲染的方案下,服務器只提供靜態資源,壓力被分攤到了訪問用戶的瀏覽器中;如果使用服務器端渲染,**每個頁面請求都要產生 HTML 頁面**,這樣服務器的壓力就會很大。
<br/>
React 并不是給服務器端渲染設計的,如果應用對 TTFP 要求不高,也不希望對 React 頁面進行搜索引擎優化,那么沒有必要使用“同構”來增加開發難度;如果希望應用的性能能更進一步,而且服務器運算資源充足,那么可以嘗試。對 Vue 而言應該也是同樣的道理。
*****
最后我們來總結下服務端渲染理論上的優缺點:
優點:
- 更快的響應時間、首屏加載時間,可以將 SEO 的關鍵信息直接在后臺渲染成 HTML,從而保證搜索引擎的爬蟲都能爬到關鍵數據
- 更快的內容到達時間,特別是對于緩慢的網絡情況或運行緩慢的設備
- 無需等待所有的 JavaScript 都完成下載并執行,才顯示服務器渲染的標記,所以用戶將會更快速地看到完整渲染的頁面,通常可以產生更好的用戶體驗
- 資源文件從本地請求(各種 bundle 什么的),更快的下載速度
缺點:
- 占用服務器更多的 CPU 和內存資源
- 一些常用的瀏覽器 API 可能無法使用,如 window、document、alert 等,如果需要使用的話需要對運行的環境加以判斷
- 開發難度加大
## Vue 項目優化點
1、第三方庫走 cdn
例如:
```html
<script src="//cdn.bootcss.com/vue/2.2.5/vue.min.js"></script>
<script src="//cdn.bootcss.com/vue-router/2.3.0/vue-router.min.js"></script>
<script src="//cdn.bootcss.com/vuex/2.2.1/vuex.min.js"></script>
<script src="//cdn.bootcss.com/axios/0.15.3/axios.min.js"></script>
```
在 webpack 里有個 externals 選項,可以忽略不需要打包的庫
[https://webpack.js.org/configuration/externals/#root](https://webpack.js.org/configuration/externals/#root)
```js
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'axios': 'axios'
},
output: {
...
}
}
```
2、路由懶加載
```js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/ebook',
component: () => import('./views/ebook/index.vue'), // 路由懶加載,這里用的是ES6的語法 import()函數是動態加載 import 是靜態加載
children: [ // 動態路由, 可以傳遞路徑參數
{
path: ':fileName',
component: () => import('./components/ebook/EbookReader.vue')
}
]
},
{
path: '/store',
component: () => import('./views/store/index.vue'),
redirect: '/store/shelf', // #/store -> #/store/home
...
}
]
})
```
3、使用懶加載插件 Vue-Loader
具體的使用可以參考 [這篇文章](https://segmentfault.com/a/1190000014928116) 或者去看官方文檔
step1:`cnpm install vue-lazyload --save`
step2:main.js導入
```js
import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyload)
```
step3:`<img class="item-pic" v-lazy="newItem.picUrl"/>`vue 文件中將需要懶加載的圖片綁定 v-bind:src 修改為 v-lazy
這只是圖片懶加載,還有很多其他可選配置
4、`v-if`與`v-show`的選擇
一般來說,`v-if`有更高的切換開銷,而`v-show`有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用`v-show`較好;如果在運行時條件很少改變,則使用`v-if`較好。
## React 項目優化點
1、單個組件的優化:更改 shouldComponentUpdate 函數的默認實現,根據每個 React 組件的內在邏輯定制其行為,減少不必要的重新渲染
```js
shouldComponentUpdate(nextProps, nextState) {
// 假設影響渲染內容的 prop 只有 completed 和 text,只需要確保
// 這兩個 prop 沒有變化,函數就可以返回 false
return (nextProps.completed !== this.props.completed) ||
(nextProps.text !== this.props.text)
}
```
2、使用 immutable.js 解決復雜數據 diff、clone 等問題。
immutable.js 實現原理:持久化數據結構,也就是使用舊數據創建新數據時,要保證舊數據同時可用且不變。同時為了避免 deepCopy 把所有節點都復制一遍帶來的性能損耗,Immutable 使用了結構共享,即如果對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享。

3、在 constructor() 里做 this 綁定
當在 render() 里使用事件處理方法時,提前在構造函數里把 this 綁定上去(如果需要的話),因為在每次 render 過程中, 再調用 bind 都會新建一個新的函數,浪費資源.
```js
// bad
class App extends React.Component {
onClickDiv() {
// do stuff
}
render() {
return <div onClick={this.onClickDiv.bind(this)} />;
}
}
// good
class App extends React.Component {
constructor(props) {
super(props);
this.onClickDiv = this.onClickDiv.bind(this);
}
onClickDiv() {
// do stuff
}
render() {
return <div onClick={this.onClickDiv} />;
}
}
```
4、基于路由的代碼分割
使用`React.lazy`和`React Router`來配置基于路由的代碼分割
```js
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
```
# 參考資料
[https://juejin.im/post/5c4a6fcd518825469414e062#heading-29](https://juejin.im/post/5c4a6fcd518825469414e062#heading-29)
[https://juejin.im/post/5cab64ce5188251b19486041#heading-6](https://juejin.im/post/5cab64ce5188251b19486041#heading-6)
[https://juejin.im/post/5d548b83f265da03ab42471d](https://juejin.im/post/5d548b83f265da03ab42471d)
[https://www.jianshu.com/p/333f390f2e84](https://www.jianshu.com/p/333f390f2e84)
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直線、矩形、多邊形
- Part2-曲線圖形
- Part3-線條操作
- Part4-文本操作
- Part5-圖像操作
- Part6-變形操作
- Part7-像素操作
- Part8-漸變與陰影
- Part9-路徑與狀態
- Part10-物理動畫
- Part11-邊界檢測
- Part12-碰撞檢測
- Part13-用戶交互
- Part14-高級動畫
- CSS
- SCSS
- codePen
- 速查表
- 面試題
- 《CSS Secrets》
- SVG
- 移動端適配
- 濾鏡(filter)的使用
- JS
- 基礎概念
- 作用域、作用域鏈、閉包
- this
- 原型與繼承
- 數組、字符串、Map、Set方法整理
- 垃圾回收機制
- DOM
- BOM
- 事件循環
- 嚴格模式
- 正則表達式
- ES6部分
- 設計模式
- AJAX
- 模塊化
- 讀冴羽博客筆記
- 第一部分總結-深入JS系列
- 第二部分總結-專題系列
- 第三部分總結-ES6系列
- 網絡請求中的數據類型
- 事件
- 表單
- 函數式編程
- Tips
- JS-Coding
- Framework
- Vue
- 書寫規范
- 基礎
- vue-router & vuex
- 深入淺出 Vue
- 響應式原理及其他
- new Vue 發生了什么
- 組件化
- 編譯流程
- Vue Router
- Vuex
- 前端路由的簡單實現
- React
- 基礎
- 書寫規范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 與 Hook
- 《深入淺出React和Redux》筆記
- 前半部分
- 后半部分
- react-transition-group
- Vue 與 React 的對比
- 工程化與架構
- Hybird
- React Native
- 新手上路
- 內置組件
- 常用插件
- 問題記錄
- Echarts
- 基礎
- Electron
- 序言
- 配置 Electron 開發環境 & 基礎概念
- React + TypeScript 仿 Antd
- TypeScript 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs