原文: 本人掘金文章
關注公眾號: 微信搜索 前端工具人
; 收貨更多的干貨
1、HTTP 優化
- 盡量減少 HTTP 請求個數
- 不阻塞情況下異步預先請求;
- 優先使用get請求, 因為頻繁刷新瀏覽器get請求不會對瀏覽器、服務器造成太大的壓力-- 不需要預檢和交互, 無傷;
- 合並請求 / 常用數據緩存代替;
- 使用 http緩存, 例如:輸出圖片、execl表格、pdf、音頻、視頻等
- 原理:http緩存都是在第二次請求開始的,第一次服務器會在資源返回的響應中攜帶上四個常用的響應頭,瀏覽器會通過判別這些響應值來決定資源緩存的狀態,再次請求的時候瀏覽器會帶上這些響應頭;
- Cache-Control (強緩存)
- 可以攜帶多個響應值,這些值可以設置緩存時間、狀態以及驗證狀態;
- public: 所有內容都將被緩存包括客戶端、代理cdn節點
- private: 只緩存到客戶端,不緩存到代理服務器
- no-cache: 需要先與服務器確認
- no-store: 所有內容都不被緩存
- max-age: 在多少秒之后失效
- Expires (強緩存)
- 標記了數據的過期時間,超過其中規定的時間后,緩存會被定義為過期,優先級Cache-Control的max-age > Expires
- ETag(協商緩存)--> 值是一個字符串(數據的哈希值),每個數據都有一個單獨的標志
- 瀏覽器會在后續的請求中攜帶上這個參數來確定緩存是否需要更新;
- 需要注意的是,ETag只有在本地緩存已過期(Expires)或者緩存模式設置為 no-cache(Cache-Control)的時候,才會被瀏覽器攜帶上服務器端的值進行判別;
- Last-Modified (協商緩存)
- 向瀏覽器發送一個數據上次被修改的時間;
- 瀏覽器就知道了該數據最后被修改的時間,后續請求中,會和服務器進行時間的比較,如果服務器上的時間比本地時間要新,說明數據有更改,瀏覽器需要重新下載數據;
- 缺點:當服務器響應中有 Expires 或者 Cache-Control 設置了 max-age 響應頭的時候,瀏覽器不會向服務器發起校驗請求,而是直接復用本地緩存。如果此時服務器進行了資源的更新,用戶就無法獲取到最新的資源,只能通過強制刷新瀏覽器緩存來跟服務器請求最新的資源
- 緩存的位置按照獲取資源請求優先級,緩存位置依次如下:
- Memory Cache(內存緩存)
是瀏覽器最先嘗試命中的緩存,也是響應最快的緩存。但是存活時間最短的,當進程結束后,tab 標簽關閉后,緩存就不存在了,因為內存空間比較小,通常較小的資源放在內存緩存中,比如 base64 圖片等資源 - Service Worker(離線緩存)
Service Worker 是一種獨立於主線程之外的 Javascript 線程。它脫離於瀏覽器窗體,因此無法直接訪問 DOM。 - Disk Cache(磁盤緩存)
內存的優先性,導致大文件不能緩存到內存中,那么磁盤緩存則不同。雖然存儲效率比內存緩存慢,但是存儲容量和存儲市場有優勢。 - Push Cache(推送緩存)它是最后一道緩存
- Memory Cache(內存緩存)
2、使用 CDN 服務器端緩存加快訪問速度 (俗稱邊緣計算)
- 原理:CDN網絡是在用戶和服務器之間增加了一層緩存層,將用戶的請求引導到最優的緩存節點就近獲取所需要的內容而不是服務器源站,從而降低網絡用塞、加塊訪問速度響應用戶的請求 (俗稱負載均衡);
- 過程:先向CDN邊緣節點發起請求 -> 檢測是否過期 -> 沒有直接返回 -> 過期則去根服務器獲取數據再返回;
- 設置:通過http響應頭中的Cache-Control和max-age的字段來設置CDN邊緣節點的數據緩存時間;
- 例子:網站中大量的css,html,js等文件、大文件的下載(圖片、視頻、音頻等),將這些靜態內容推送到CDN節點;
- 構成:初始服務器,分布於各個節點的緩存服務器,重定向DNS服務器和內容交換服務器
- 主要技術:
- 負載均衡;
- 內容存儲技術 (內容源的存儲、內容在cache節點中的分布式存儲);
- 內容分發技術(構建網絡,將鏈接到IP網絡上的內容,快速的傳輸到用戶終端)
- 缺點:當源服務器資源更新后,如果 CDN 節點上緩存數據還未過期,用戶訪問到的依舊是過期的緩存資源,這會導致用戶最終訪問出現偏差。因此,開發者需要手動刷新相關資源,使 CDN 緩存保持為最新的狀態
3. 減少 DNS 查找次數
- DNS用於映射主機名和IP地址, DNS解析有代價,一般一次解析需要20~120毫秒。瀏覽器在DNS查詢完成前不會下載任何東西,所以瀏覽器會想辦法對DNS的查找結果進行緩存
- 減少域名主機可減少DNS查詢的次數,最理想的方法就是將所有的內容資源都放在同一個域(Domain)下面,這樣訪問整個網站就只需要進行一次DNS查找,這樣可以提高性能。
- 在HTTP /1.1中放在同個域下面會帶來一定數量的並行度(它的建議是2),那么就會出現下載資源時的排隊現象,這樣就會降低性能,推薦客戶端針對每個域在一個網站里面使用至少2個域,但不多於4個域
4. 圖片 優化
- 相對應的切換優先使用雪碧圖
- 優先使用font字體、svg、base64、JPG、JPEG、WEBP格式的圖片
- 列表圖片使用預加載、懶加載、及脫離文檔流后進行DOM回收
- 使用http、cdn緩存、不失幀情況下對圖片壓縮
5. 渲染 優化
- SSR(優化首頁渲染時間)、骨架屏、開啟gzip壓縮、js混淆(無效字符及注釋的刪除、碼語義的縮減和優化)、css壓縮、合並css資源
- 減少重定向、減少外鏈、不濫用web字體、css的文件放在頭部,js文件放在尾部或者異步(async和defer、動態腳本創建) ( 標簽 preload 渲染前加載,prefetch,dns-prefetch渲染完成后空閑時間加載 )
- 盡量避免內聯樣式、避免html里執行js
- 盡量使用css動畫、減少css表達式、使用requestAnimationFrame操作動畫
6. DOM 優化
- 避免重繪重排、減少 DOM 元素個數
- 批量操作DOM,並且脫離文檔流后在操作
- 多次修改樣式、結構。盡量合並在一起修改
- 使用css3 GPU硬件加速,translate3d、translateZ、rotate、scale、transform、opacity、filters等動畫效果不會引起回流重繪
- 對於頻繁操作(例如:輸入框、scroll監聽)使用節流、防抖
- 節流:短時間內大量觸發同一事件,在函數執行一次之后,該函數在指定的時間期限內不再執行,直至過了這段時間才重新生效(例如監聽滾動條)
- 防抖:如果短時間內大量觸發同一事件,只會執行一次函數(例如:input事件)
7. 使用JSON格式來進行數據交換
JSON是一種輕量級的數據交換格式,采用完全獨立於語言的文本格式,是理想的數據交換格式。同時,JSON是 JavaScript原生格式,這意味着在 JavaScript 中處理 JSON數據不需要任何特殊的 API 或工具包
8. 控制Cookie大小和污染
- Cookie是本地的磁盤文件,每次瀏覽器都會去讀取相應的Cookie,所以建議去除不必要的Coockie,使Coockie體積盡量小
- 使用Cookie跨域操作時注意在適應級別的域名上設置coockie以便使子域名不受其影響
- Cookie是有生命周期的,所以請注意設置合理的過期時間,合理地Expire時間和不要過早去清除coockie
9. 避免 404、減少 DOM 訪問、用 代替 @import、保持單個內容小於25K
10. vue 優化
- v-if (從DOM樹中刪除、成本大嗎,會重繪重排) 和 v-show (適應於頻繁的操作顯示隱藏)
- computed (值有緩存,只有它依賴的屬性值發生改變 和 watch(對數據的監聽回調,應做防抖節流操作) 區分使用場景
- v-for 遍歷必須為 item 添加 key,且避免同時使用 v-if
- 長列表性能優化 (Vue 會對數據進行劫持,實現雙向數據綁定,但有的時候就說純粹的數據展示,所以應避免vue靜態數據進行劫持, Object.freeze 方法來凍結一個對象,一旦被凍結的對象就再也不能被修改了)
事件的銷毀、路由懶加載、第三方插件的按需引入 - 服務端渲染 SSR (vue-server-renderer) 或者 預渲染 (prerender-spa-plugin)
- prerender-spa-plugin 利用了 Puppeteer 的爬取頁面的功能, Puppeteer 是一個 Chrome官方出品的 headlessChromenode 庫,在 Webpack 構建階段的最后,在本地啟動一個 Puppeteer 的服務,訪問配置了預渲染的路由,然后將 Puppeteer 中渲染的頁面輸出到 HTML 文件中,並建立路由對應的目錄
- assetsPublicPath原先寫的是/dist,導致body渲染不出來,改成/
- 插件在webpack此 "webpack": "^4.6.0" 版本下不支持路由懶加載, 4.28.4版本才修復
- 預渲染只能保證靜態部分不更改, 要動態數據的話,webpack的devserver代理數據無效,需要用nginx或者其他代理工具代理線上數據
- vue-server-renderer 利用服務端渲染,當你看的頁面的時候,實際頁面已經有服務端幫你渲染完成直接返回給瀏覽器
- 提高了用戶體驗,能快速的瀏覽的所需頁面、SEO 友好
- 減輕了瀏覽器壓力,但相應的造成了服務器的壓力;
- 開發成本,需要熟悉node.js、部署等后端知識。只能使用beforeCreated、created 兩個生命周期函數
- 缺點:
- 在數據預獲取階段注冊的鈎子函數中,最好只進行數據的獲取和保存,不進行其他任何涉及this的操作。因為此時的this是服務端的this,是所有用戶共享的this,進行操作將發生一些不可預知的錯誤
- Vuex在服務端預獲取數據,Vuex應使用惰性注冊的方案,避免初始化實例的時候就把所有的模塊統一注冊,將會出現多個頁面共用許多模塊的問題
- 需要手動拿到客戶端的cookie傳給后端服務器cookie穿透
- 路由模式:ssr的路由需要采用history的方式。hash模式的路由提交不到服務器上
- DDOS攻擊:利用合理的服務請求來占用過多的服務資源,從而使合法用戶無法得到服務的響應(解決:在服務端不做過多復雜的數據處理、DDOS軟硬件防火牆)
- sql注入:對參數進行校驗進行避免;
- 數據泄露:使用vuex的情況下,如果不使用惰性加載,容易造成數據泄露的情況發生(全局的this)。
- prerender-spa-plugin 利用了 Puppeteer 的爬取頁面的功能, Puppeteer 是一個 Chrome官方出品的 headlessChromenode 庫,在 Webpack 構建階段的最后,在本地啟動一個 Puppeteer 的服務,訪問配置了預渲染的路由,然后將 Puppeteer 中渲染的頁面輸出到 HTML 文件中,並建立路由對應的目錄
11. React 優化 (性能主要耗費在於update階段的diff算法,因此性能優化也主要針對diff算法)
- 減少diff算法觸發次數(實際上減少update流程的次數)
- setState 機制是批更新策略,已經降低了update過程的觸發次數,盡量無論數據處理多么復雜,保證最后只調用一次setState
- 父組件render 父組件的render必然會觸發子組件進入update階段(無論props是否更新)。盡量在shouldComponentUpdate / PureComponent方法處理, 最常見的方式為進行this.props和this.state的淺比較來判斷組件是否需要更新( 淺判等 只會比較到兩個對象的 ownProperty 是否符合 Object.is() 判等,不會遞歸地去深層比較)
- forceUpdate 調用后將會直接進入componentWillUpdate階段,無法攔截,因此在實際項目中應該棄用
- shouldComponentUpdate 是決定react組件什么時候能夠不重新渲染的函數, 減少不必要的props變化導致的渲染。如一個不用於渲染的props導致的update
- 正確使用 diff算法
- 不使用跨層級移動節點的操作
- 對於條件渲染多個節點時,盡量采用隱藏等方式切換節點,而不是替換節點
- 盡量避免將后面的子節點移動到前面的操作,當節點數量較多時,會產生一定的性能問題。
12. Webpack 優化
- Webpack 對圖片進行壓縮:
image-webpack-loader 插件
- 減少 ES6 轉為 ES5 的冗余代碼
babel-plugin-transform-runtime / Tree-Shaking 插件, 修改 .babelrc 配置plugins: "transform-runtime" - 模板預編譯
vue-template-loader插件
- 優化 SourceMap
- 開發環境推薦:cheap-module-eval-source-map
- 生產環境推薦:cheap-module-source-map
- 構建結果輸出分析:
webpack-bundle-analyzer 插件
- 使用CDN加速靜態資源加載
output: 配置存放靜態文件的CDN地址 - 給 loader 減輕負擔
用 include 或 exclude 來幫我們避免不必要的轉譯 module --> rules配置; 開啟緩存將轉譯結果緩存至文件系統 loader: 'babel-loader?cacheDirectory=true' - 使用 Happypack 將 loader 由單進程轉為多進程
webpack 的缺點是單線程的,我們可以使用 Happypack 把任務分解給多個子進程去並發執行,大大提升打包效率。配置的方法是把 loader 的配置轉移到 HappyPack 中去。 - DllPlugin 提取公用庫
開發過程中,我們經常需要引入大量第三方庫,這些庫並不需要隨時修改或調試,我們可以使用DllPlugin和DllReferencePlugin單獨構建它們,配置webpack.dll.config.js - externals 選項
以使用 externals 讓 webpack 不打包某部分,然后在其他地方引入 cdn 上的 js 文件,利用緩存下載 cdn 文件達到減少打包時間的目的。++webpack.prod.config.js++ 配置 externals 選項 - 構建體積壓縮
可以使用vue-cli4或者webpack-bundle-analyzer生成構建統計報告- 代碼分割 splitChunks
- 業務代碼和第三方庫分離打包,實現代碼分割;
- 業務代碼中的公共業務模塊提取打包到一個模塊;
- 第三方庫最好也不要全部打包到一個文件中,因為第三方庫加起來通常會很大,我會把一些特別大的庫分別獨立打包,剩下的加起來如果還很大,就把它按照一定大小切割成若干模塊。
- 刪除冗余代碼 --> Tree-Shaking插件
- UI 庫 按需加載 --> babel-plugin-component插件
- 懶加載 --> @babel/plugin-syntax-dynamic-import 插件
- 代碼分割 splitChunks
// webpack-bundle-analyzer 配置
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
module.exports = {
plugins: [new BundleAnalyzerPlugin()],
};
- 開啟gzip壓縮
//nginx配置開啟gzip壓縮,nginx會根據配置情況對指定的類型文件進行壓縮。主要針對js與css。如果文件路徑中存在與原文件同名(加了個.gz),nginx會獲取gz文件,如果找不到,會主動進行gzip壓縮
// nginx 配置
gzip on; #開啟或關閉gzip on off
gzip_disable "msie6"; #不使用gzip IE6
gzip_min_length 100k; #gzip壓縮最小文件大小,超出進行壓縮(自行調節)
gzip_buffers 4 16k; #buffer 不用修改
gzip_comp_level 8; #壓縮級別:1-10,數字越大壓縮的越好,時間也越長
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; # 壓縮文件類型
gzip_vary off;
13. 多使用 Chrome Performance 的火焰圖 查找性能瓶頸(評測報告中FP、FCP、FMP、LCP、TTI、TTFB、FCI、FID、DCL、Speed Index)
- FP ++"首次繪制"++ 是第一個“時間點”,它代表瀏覽器第一次向屏幕傳輸像素的時間,就是頁面在屏幕上首次發生視覺變化的時間。
- FCP ++"首次內容繪制"++, 代表瀏覽器第一次向屏幕繪制 “內容” (只有首次繪制文本、圖片(包含背景圖)、非白色的canvas或SVG時才被算作 FCP)
- FP和FCP可能是相同的時間,也可能是先FP后FCP。
- FMP ++"首次有效繪制"++ 主要內容”開始出現在屏幕上的時間點。它是我們測量用戶加載體驗的主要指標
- LCP 可視區“內容”最大的可見元素開始出現在屏幕上的時間點。
- TTI ++"可交互時間"++ 網頁第一次 完全達到可交互狀態 的時間點
- TTFB 表示瀏覽器接收第一個字節的時間
- FCI 告訴我們頁面什么時候完全達到可用
- FID FID指的是用戶首次與產品進行交互時,我們產品可以在多長時間給出反饋
- DCL DomContentloaded事件觸發的時間
- Speed Index 頁面可見部分的平均時間
- 商城、官網、博客這種頁面更側重FMP(用戶希望盡快看到有價值的內容),而類似后台管理系統或在線PPT這種產品則更側重TTI(用戶希望盡快與產品進行交互)。