講武德,你們要的7000字前端性能優(yōu)化干貨 ,來了
為什么要做性能優(yōu)化?性能優(yōu)化到底有多重要? 網(wǎng)站的性能優(yōu)化對于用戶的留存率、轉(zhuǎn)化率有很大的影響,所以對于前端開發(fā)來說性能優(yōu)化能力也是重要的考察點。
性能優(yōu)化的點非常的多,有的小伙伴覺得記起來非常的麻煩,所以這里主要梳理出一條線來幫助記憶。
可以將性能優(yōu)化分為兩個大的分類:
加載時優(yōu)化 運行時優(yōu)化
加載時性能
顧名思義加載時優(yōu)化 主要解決的就是讓一個網(wǎng)站加載過程更快,比如壓縮文件大小、使用CDN加速等方式可以優(yōu)化加載性能。檢查加載性能的指標一般看:白屏時間和首屏時間:
白屏時間:指的是從輸入網(wǎng)址, 到頁面開始顯示內(nèi)容的時間。 首屏時間:指從輸入網(wǎng)址, 到首屏頁面內(nèi)容渲染完畢的時間。
白屏時間計算
將代碼腳本放在 </head> 前面就能獲取白屏時間:
<script>
new Date().getTime() - performance.timing.navigationStart
</script>
首屏時間計算
在window.onload事件中執(zhí)行以下代碼,可以獲取首屏時間:
new Date().getTime() - performance.timing.navigationStart
運行時性能
運行時性能是指頁面運行時的性能表現(xiàn),而不是頁面加載時的性能。可以通過chrome開發(fā)者工具中的 Performance 面板來分析頁面的運行時性能。關(guān)于chrome開發(fā)者工具具體如何操作以及如何查看性能,可以看這篇文章性能優(yōu)化篇——運行時性能分析
接下來就從加載時性能和運行時性能兩個方面來討論網(wǎng)站優(yōu)化具體應(yīng)該怎么做。
加載時性能優(yōu)化
我們知道瀏覽器如果輸入的是一個網(wǎng)址,首先要交給DNS域名解析 -> 找到對應(yīng)的IP地址 -> 然后進行TCP連接 -> 瀏覽器發(fā)送HTTP請求 -> 服務(wù)器接收請求 -> 服務(wù)器處理請求并返回HTTP報文 -> 以及瀏覽器接收并解析渲染頁面。從這一過程中,其實就可以挖出優(yōu)化點,縮短請求的時間,從而去加快網(wǎng)站的訪問速度,提升性能。
這個過程中可以提升性能的優(yōu)化的點:
DNS解析優(yōu)化,瀏覽器訪問DNS的時間就可以縮短 使用HTTP2 減少HTTP請求數(shù)量 減少http請求大小 服務(wù)器端渲染 靜態(tài)資源使用CDN 資源緩存,不重復(fù)加載相同的資源
從上面幾個優(yōu)化點出發(fā),有以下幾種實現(xiàn)性能優(yōu)化的方式。
1.DNS 預(yù)解析
DNS 作為互聯(lián)網(wǎng)的基礎(chǔ)協(xié)議,其解析的速度似乎容易被網(wǎng)站優(yōu)化人員忽視。現(xiàn)在大多數(shù)新瀏覽器已經(jīng)針對DNS解析進行了優(yōu)化,典型的一次DNS解析耗費20-120毫秒,減少DNS解析時間和次數(shù)是個很好的優(yōu)化方式。DNS Prefetching是具有此屬性的域名不需要用戶點擊鏈接就在后臺解析,而域名解析和內(nèi)容載入是串行的網(wǎng)絡(luò)操作,所以這個方式能減少用戶的等待時間,提升用戶體驗。
瀏覽器對網(wǎng)站第一次的域名DNS解析查找流程依次為:
瀏覽器緩存 ->系統(tǒng)緩存 ->路由器緩存 ->ISP DNS緩存 ->遞歸搜索
DNS預(yù)解析的實現(xiàn):
用meta信息來告知瀏覽器, 當前頁面要做DNS預(yù)解析:
<meta http-equiv="x-dns-prefetch-control" content="on" />
在頁面header中使用link標簽來強制對DNS預(yù)解析:
<link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />
注意:dns-prefetch需慎用,多頁面重復(fù)DNS預(yù)解析會增加重復(fù)DNS查詢次數(shù)。
2.使用HTTP2
HTTP2帶來了非常大的加載優(yōu)化,所以在做優(yōu)化上首先就想到了用HTTP2代替HTTP1。
HTTP2相對于HTTP1有這些優(yōu)點:
解析速度快
服務(wù)器解析 HTTP1.1 的請求時,必須不斷地讀入字節(jié),直到遇到分隔符 CRLF 為止。而解析 HTTP2 的請求就不用這么麻煩,因為 HTTP2 是基于幀的協(xié)議,每個幀都有表示幀長度的字段。
多路復(fù)用
在 HTTP2 上,多個請求可以共用一個 TCP 連接,這稱為多路復(fù)用。
當然HTTP1.1有一個可選的Pipelining技術(shù),說的意思是當一個HTTP連接在等待接收響應(yīng)時可以通過這個連接發(fā)送其他請求。聽起來很棒,其實這里有一個坑,處理響應(yīng)是按照順序的,也就是后發(fā)的請求有可能被先發(fā)的阻塞住,也正因此很多瀏覽器默認是不開啟Pipelining的。
HTTP1 的Pipelining技術(shù)會有阻塞的問題,HTTP/2的多路復(fù)用可以粗略的理解為非阻塞版的Pipelining。即可以同時通過一個HTTP連接發(fā)送多個請求,誰先響應(yīng)就先處理誰,這樣就充分的壓榨了TCP這個全雙工管道的性能。加載性能會是HTTP1的幾倍,需要加載的資源越多越明顯。當然多路復(fù)用是建立在加載的資源在同一域名下,不同域名神仙也復(fù)用不了。
首部壓縮
HTTP2 提供了首部壓縮功能。(這部分了解一下就行)
HTTP 1.1請求的大小變得越來越大,有時甚至會大于TCP窗口的初始大小,因為它們需要等待帶著ACK的響應(yīng)回來以后才能繼續(xù)被發(fā)送。HTTP/2對消息頭采用HPACK(專為http/2頭部設(shè)計的壓縮格式)進行壓縮傳輸,能夠節(jié)省消息頭占用的網(wǎng)絡(luò)的流量。而HTTP/1.x每次請求,都會攜帶大量冗余頭信息,浪費了很多帶寬資源。
服務(wù)器推送
服務(wù)端可以在發(fā)送頁面HTML時主動推送其它資源,而不用等到瀏覽器解析到相應(yīng)位置,發(fā)起請求再響應(yīng)。
3.減少HTTP請求數(shù)量
HTTP請求建立和釋放需要時間。
HTTP請求從建立到關(guān)閉一共經(jīng)過以下步驟:
客戶端連接到Web服務(wù)器 發(fā)送HTTP請求 服務(wù)器接受請求并返回HTTP響應(yīng) 釋放連接TCP鏈接
這些步驟都是需要花費時間的,在網(wǎng)絡(luò)情況差的情況下,花費的時間更長。如果頁面的資源非常碎片化,每個HTTP請求只帶回來幾K甚至不到1K的數(shù)據(jù)(比如各種小圖標)那性能是非常浪費的。
4.壓縮、合并文件
壓縮文件 -> 減少HTTP請求大小,可以減少請求時間 文件合并 -> 減少HTTP請求數(shù)量。
我們可以對html、css、js以及圖片資源進行壓縮處理,現(xiàn)在可以很方便的使用 webpack 實現(xiàn)文件的壓縮:
js壓縮:UglifyPlugin CSS壓縮:MiniCssExtractPlugin HTML壓縮:HtmlWebpackPlugin 圖片壓縮:image-webpack-loader
提取公共代碼
合并文件雖然能減少HTTP請求數(shù)量, 但是并不是文件合并越多越好,還可以考慮按需加載方式(后面第6點有講到)。什么樣的文件可以合并呢?可以提取項目中多次使用到的公共代碼進行提取,打包成公共模塊。
可以使用 webpack4 的 splitChunk 插件 cacheGroups 選項。
optimization: {
runtimeChunk: {
name: 'manifest' // 將 webpack 的 runtime 代碼拆分為一個單獨的 chunk。
},
splitChunks: {
cacheGroups: {
vendor: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
},
}
},
5.采用svg圖片或者字體圖標
因為字體圖標或者SVG是矢量圖,代碼編寫出來的,放大不會失真,而且渲染速度快。字體圖標使用時就跟字體一樣,可以設(shè)置屬性,例如 font-size、color 等等,非常方便,還有一個優(yōu)點是生成的文件特別小。
6.按需加載代碼,減少冗余代碼
按需加載
在開發(fā)SPA項目時,項目中經(jīng)常存在十幾個甚至更多的路由頁面, 如果將這些頁面都打包進一個JS文件, 雖然減少了HTTP請求數(shù)量, 但是會導致文件比較大,同時加載了大量首頁不需要的代碼,有些得不償失,這時候就可以使用按需加載, 將每個路由頁面單獨打包為一個文件,當然不僅僅是路由可以按需加載。
根據(jù)文件內(nèi)容生成文件名,結(jié)合 import 動態(tài)引入組件實現(xiàn)按需加載:
通過配置 output 的 filename 屬性可以實現(xiàn)這個需求。filename 屬性的值選項中有一個 [contenthash],它將根據(jù)文件內(nèi)容創(chuàng)建出唯一 hash。當文件內(nèi)容發(fā)生變化時,[contenthash] 也會發(fā)生變化。
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
path: path.resolve(__dirname, '../dist'),
},
減少冗余代碼
一方面避免不必要的轉(zhuǎn)義:babel-loader用 include 或 exclude 來幫我們避免不必要的轉(zhuǎn)譯,不轉(zhuǎn)譯node_moudules中的js文件,其次在緩存當前轉(zhuǎn)譯的js文件,設(shè)置loader: 'babel-loader?cacheDirectory=true'
其次減少ES6 轉(zhuǎn)為 ES5 的冗余代碼:Babel 轉(zhuǎn)化后的代碼想要實現(xiàn)和原來代碼一樣的功能需要借助一些幫助函數(shù),比如:
class Person {}
會被轉(zhuǎn)換為:
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Person = function Person() {
_classCallCheck(this, Person);
};
這里 _classCallCheck 就是一個 helper 函數(shù),如果在很多文件里都聲明了類,那么就會產(chǎn)生很多個這樣的 helper 函數(shù)。
這里的 @babel/runtime 包就聲明了所有需要用到的幫助函數(shù),而 @babel/plugin-transform-runtime 的作用就是將所有需要 helper 函數(shù)的文件,從 @babel/runtime包 引進來:
"use strict";
var _classCallCheck2 = require("@babel/runtime/helpers/classCallCheck");
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
var Person = function Person() {
(0, _classCallCheck3.default)(this, Person);
};
這里就沒有再編譯出 helper 函數(shù) classCallCheck 了,而是直接引用了@babel/runtime 中的 helpers/classCallCheck。
安裝
npm i -D @babel/plugin-transform-runtime @babel/runtime使用 在 .babelrc 文件中
"plugins": [
"@babel/plugin-transform-runtime"
]
7.服務(wù)器端渲染
客戶端渲染: 獲取 HTML 文件,根據(jù)需要下載 JavaScript 文件,運行文件,生成 DOM,再渲染。
服務(wù)端渲染:服務(wù)端返回 HTML 文件,客戶端只需解析 HTML。
優(yōu)點:首屏渲染快,SEO 好。缺點:配置麻煩,增加了服務(wù)器的計算壓力。
8. 使用 Defer 加載JS
盡量將 CSS 放在文件頭部,JavaScript 文件放在底部
所有放在 head 標簽里的 CSS 和 JS 文件都會堵塞渲染。如果這些 CSS 和 JS 需要加載和解析很久的話,那么頁面就空白了。所以 JS 文件要放在底部,等 HTML 解析完了再加載 JS 文件。
那為什么 CSS 文件還要放在頭部呢?
因為先加載 HTML 再加載 CSS,會讓用戶第一時間看到的頁面是沒有樣式的、“丑陋”的,為了避免這種情況發(fā)生,就要將 CSS 文件放在頭部了。
另外,JS 文件也不是不可以放在頭部,只要給 script 標簽加上 defer 屬性就可以了,異步下載,延遲執(zhí)行。
9. 靜態(tài)資源使用 CDN
用戶與服務(wù)器的物理距離對響應(yīng)時間也有影響。把內(nèi)容部署在多個地理位置分散的服務(wù)器上能讓用戶更快地載入頁面, CDN就是為了解決這一問題,在多個位置部署服務(wù)器,讓用戶離服務(wù)器更近,從而縮短請求時間。

10. 圖片優(yōu)化
雪碧圖
圖片可以合并么?當然。最為常用的圖片合并場景就是雪碧圖(Sprite)。
在網(wǎng)站上通常會有很多小的圖標,不經(jīng)優(yōu)化的話,最直接的方式就是將這些小圖標保存為一個個獨立的圖片文件,然后通過 CSS 將對應(yīng)元素的背景圖片設(shè)置為對應(yīng)的圖標圖片。這么做的一個重要問題在于,頁面加載時可能會同時請求非常多的小圖標圖片,這就會受到瀏覽器并發(fā) HTTP 請求數(shù)的限制。
雪碧圖的核心原理在于設(shè)置不同的背景偏移量,大致包含兩點:
不同的圖標元素都會將 background-url設(shè)置為合并后的雪碧圖的 uri;不同的圖標通過設(shè)置對應(yīng)的 background-position來展示大圖中對應(yīng)的圖標部分。你可以用 Photoshop 這類工具自己制作雪碧圖。當然比較推薦的還是將雪碧圖的生成集成到前端自動化構(gòu)建工具中,例如在webpack中使用webpack-spritesmith,或者在gulp中使用gulp.spritesmith。它們兩者都是基于spritesmith這個庫。
圖片懶加載
一般來說,我們訪問網(wǎng)站頁面時,其實很多圖片并不在首屏中,如果我們都加載的話,相當于是加載了用戶不一定會看到圖片, 這顯然是一種浪費。解決的核心思路就是懶加載:實現(xiàn)方式就是先不給圖片設(shè)置路徑,當圖片出現(xiàn)在瀏覽器可視區(qū)域時才設(shè)置真正的圖片路徑。
實現(xiàn)上就是先將圖片路徑設(shè)置給original-src,當頁面不可見時,圖片不會加載:
<img original-src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9eb06680a16044feb794f40fc3b1ac3d~tplv-k3u1fbpfcp-watermark.image" />
通過監(jiān)聽頁面滾動,等頁面可見時設(shè)置圖片src:
const img = document.querySelector('img')
img.src = img.getAttribute("original-src")
如果想使用懶加載,還可以借助一些已有的工具庫,例如 aFarkas/lazysizes、verlok/lazyload、tuupola/lazyload 等。
css中圖片懶加載
除了對于 <img> 元素的圖片進行來加載,在 CSS 中使用的圖片一樣可以懶加載,最常見的場景就是 background-url。
.login {
background-url: url(/static/img/login.png);
}
對于上面這個樣式規(guī)則,如果不應(yīng)用到具體的元素,瀏覽器不會去下載該圖片。所以你可以通過切換 className 的方式,放心得進行 CSS 中圖片的懶加載。
運行時性能優(yōu)化
1. 減少重繪與重排
有前端經(jīng)驗的開發(fā)者對這個概念一定不會陌生,瀏覽器下載完頁面需要的所有資源后, 就開始渲染頁面,主要經(jīng)歷這5個過程:
解析HTML生成DOM樹 解析CSS生成CSSOM規(guī)則樹 將DOM樹與CSSOM規(guī)則樹合并生成Render(渲染)樹 遍歷Render(渲染)樹開始布局, 計算每一個節(jié)點的位置大小信息 將渲染樹每個節(jié)點繪制到屏幕上

重排
當改變DOM元素位置或者大小時, 會導致瀏覽器重新生成Render樹, 這個過程叫重排
重繪
當重新生成渲染樹后, 將要將渲染樹每個節(jié)點繪制到屏幕, 這個過程叫重繪。
重排觸發(fā)時機
重排發(fā)生后的根本原理就是元素的幾何屬性發(fā)生改變, 所以從能夠改變幾何屬性的角度入手:
添加|刪除可見的DOM元素 元素位置發(fā)生改變 元素本省的尺寸發(fā)生改變 內(nèi)容變化 頁面渲染器初始化 瀏覽器窗口大小發(fā)生改變
二者關(guān)系:重排會導致重繪, 但是重繪不會導致重排
了解了重排和重繪這兩個概念,我們還要知道重排和重繪的開銷都是非常昂貴的,如果不停的改變頁面的布局,就會造成瀏覽器消耗大量的開銷在進行頁面的計算上,這樣容易造成頁面卡頓。那么回到我們的問題如何減少重繪與重排呢?
1.1 避免table布局
不要使用table布局,可能很小的一個改動會造成整個table重新布局
1.2 分離讀寫操作
DOM 的多個讀操作(或多個寫操作),應(yīng)該放在一起。不要兩個讀操作之間,加入一個寫操作。
// bad 強制刷新 觸發(fā)四次重排+重繪
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
div.style.right = div.offsetRight + 1 + 'px';
div.style.bottom = div.offsetBottom + 1 + 'px';
// good 緩存布局信息 相當于讀寫分離 觸發(fā)一次重排+重繪
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
var curRight = div.offsetRight;
var curBottom = div.offsetBottom;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
div.style.right = curRight + 1 + 'px';
div.style.bottom = curBottom + 1 + 'px';
1.3 樣式集中改變
不要頻發(fā)的操作樣式,雖然現(xiàn)在大部分瀏覽器有渲染隊列優(yōu)化,但是在一些老版本的瀏覽器仍然存在效率低下的問題:
// 三次重排
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
// 一次重排
el.style.cssText = 'left: 10px;top: 10px; width: 20px';
或者可以采用更改類名而不是修改樣式的方式。
1.4 position屬性為absolute或fixed
使用絕對定位會使的該元素單獨成為渲染樹中 body 的一個子元素,重排開銷比較小,不會對其它節(jié)點造成太多影響。當你在這些節(jié)點上放置這個元素時,一些其它在這個區(qū)域內(nèi)的節(jié)點可能需要重繪,但是不需要重排。
2. 避免頁面卡頓
我們目前大多數(shù)屏幕的刷新率-60次/s,瀏覽器渲染更新頁面的標準幀率也為60次/s --60FPS(frames/pre second), 那么每一幀的預(yù)算時間約為16.6ms ≈ 1s/60,瀏覽器在這個時間內(nèi)要完成所有的整理工作,如果無法符合此預(yù)算, 幀率將下降,內(nèi)容會在屏幕抖動, 此現(xiàn)象通常稱為卡頓。
瀏覽器需要做的工作包含下面這個流程:
首先你用js做了些邏輯,還觸發(fā)了樣式變化,style把應(yīng)用的樣式規(guī)則計算好之后,把影響到的頁面元素進行重新布局,叫做layout,再把它畫到內(nèi)存的一個畫布里面,paint成了像素,最后把這個畫布刷到屏幕上去,叫做composite,形成一幀。
這幾項的任何一項如果執(zhí)行時間太長了,就會導致渲染這一幀的時間太長,平均幀率就會掉。假設(shè)這一幀花了50ms,那么此時的幀率就為1s / 50ms = 20fps.
當然上面的過程并不一定每一步都會執(zhí)行,例如:
你的js只是做一些運算,并沒有增刪DOM或改變CSS,那么后續(xù)幾步就不會執(zhí)行 style只改了顏色等不需要重新layout的屬性就不用執(zhí)行layout這一步style改了transform屬性,在blink和edge瀏覽器里面不需要layout和paint
3. 長列表優(yōu)化
有時會有這樣的需求, 需要在頁面上展示包含上百個元素的列表(例如一個Feed流)。每個列表元素還有著復(fù)雜的內(nèi)部結(jié)構(gòu),這顯然提高了頁面渲染的成本。當你使用了React時,長列表的問題就會被進一步的放大。那么怎么來優(yōu)化長列表呢?
1.1 實現(xiàn)虛擬列表
虛擬列表是一種用來優(yōu)化長列表的技術(shù)。它可以保證在列表元素不斷增加,或者列表元素很多的情況下,依然擁有很好的滾動、瀏覽性能。它的核心思想在于:只渲染可見區(qū)域附近的列表元素。下圖左邊就是虛擬列表的效果,可以看到只有視口內(nèi)和臨近視口的上下區(qū)域內(nèi)的元素會被渲染。

具體實現(xiàn)步驟如下所示:
首先確定長列表所在父元素的大小,父元素的大小決定了可視區(qū)的寬和高 確定長列表每一個列表元素的寬和高,同時初始的條件下計算好長列表每一個元素相對于父元素的位置,并用一個數(shù)組來保存所有列表元素的位置信息 首次渲染時,只展示相對于父元素可視區(qū)內(nèi)的子列表元素,在滾動時,根據(jù)父元素的滾動的 offset重新計算應(yīng)該在可視區(qū)內(nèi)的子列表元素。這樣保證了無論如何滾動,真實渲染出的dom節(jié)點只有可視區(qū)內(nèi)的列表元素。假設(shè)可視區(qū)內(nèi)能展示5個子列表元素,及時長列表總共有1000個元素,但是每時每刻,真實渲染出來的dom節(jié)點只有5個。 補充說明,這種情況下,父元素一般使用 position:relative,子元素的定位一般使用:position:absolute或sticky
除了自己實現(xiàn)外, 常用的框架也有不錯的開源實現(xiàn), 例如:
基于React的 react-virtualized基于Vue 的 vue-virtual-scroll-list基于Angular的 ngx-virtual-scroller
4. 滾動事件性能優(yōu)化
前端最容易碰到的性能問題的場景之一就是監(jiān)聽滾動事件并進行相應(yīng)的操作。由于滾動事件發(fā)生非常頻繁,所以頻繁地執(zhí)行監(jiān)聽回調(diào)就容易造成JavaScript執(zhí)行與頁面渲染之間互相阻塞的情況。
對應(yīng)滾動這個場景,可以采用防抖和節(jié)流來處理。
當一個事件頻繁觸發(fā),而我們希望間隔一定的時間再觸發(fā)相應(yīng)的函數(shù)時, 就可以使用節(jié)流(throttle)來處理。比如判斷頁面是否滾動到底部,然后展示相應(yīng)的內(nèi)容;就可以使用節(jié)流,在滾動時每300ms進行一次計算判斷是否滾動到底部的邏輯,而不用無時無刻地計算。
當一個事件頻繁觸發(fā),而我們希望在事件觸發(fā)結(jié)束一段時間后(此段時間內(nèi)不再有觸發(fā))才實際觸發(fā)響應(yīng)函數(shù)時會使用防抖(debounce)。例如用戶一直點擊按鈕,但你不希望頻繁發(fā)送請求,你就可以設(shè)置當點擊后 200ms 內(nèi)用戶不再點擊時才發(fā)送請求。
對節(jié)流和防抖不太了解的可以看這篇文章:老生常談的防抖與節(jié)流https://mp.weixin.qq.com/s/HVkV7F1U77GvXbEI9MWA6g
5. 使用 Web Workers
前面提到了大量數(shù)據(jù)的渲染環(huán)節(jié)我們可以采用虛擬列表的方式實現(xiàn),但是大量數(shù)據(jù)的計算環(huán)節(jié)依然會產(chǎn)生瀏覽器假死或者卡頓的情況.
通常情況下我們CPU密集型的任務(wù)都是交給后端計算的,但是有些時候我們需要處理一些離線場景或者解放后端壓力,這個時候此方法就不奏效了.
還有一種方法是計算切片,使用 setTimeout 拆分密集型任務(wù),但是有些計算無法利用此方法拆解,同時還可能產(chǎn)生副作用,這個方法需要視具體場景而動.
最后一種方法也是目前比較奏效的方法就是利用Web Worker 進行多線程編程.
Web Worker 是一個獨立的線程(獨立的執(zhí)行環(huán)境),這就意味著它可以完全和 UI 線程(主線程)并行的執(zhí)行 js 代碼,從而不會阻塞 UI,它和主線程是通過 onmessage 和 postMessage 接口進行通信的。
Web Worker 使得網(wǎng)頁中進行多線程編程成為可能。當主線程在處理界面事件時,worker 可以在后臺運行,幫你處理大量的數(shù)據(jù)計算,當計算完成,將計算結(jié)果返回給主線程,由主線程更新 DOM 元素。
6. 寫代碼時的優(yōu)化點
提升性能,有時候在我們寫代碼時注意一些細節(jié)也是有效果的。
6.1 使用事件委托
看一下下面這段代碼:
<ul>
<li>字節(jié)跳動</li>
<li>阿里</li>
<li>騰訊</li>
<li>京東</li>
</ul>
// good
document.querySelector('ul').onclick = (event) => {
const target = event.target
if (target.nodeName === 'LI') {
console.log(target.innerHTML)
}
}
// bad
document.querySelectorAll('li').forEach((e) => {
e.onclick = function() {
console.log(this.innerHTML)
}
})
綁定的事件越多, 瀏覽器內(nèi)存占有就越多,從而影響性能,利用事件代理的方式就可節(jié)省一些內(nèi)存。
6.2 if-else 對比 switch
當判定條件越來越多時, 越傾向于使用switch,而不是if-else:
if (state ==0) {
console.log("待開通")
} else if (state == 1) {
console.log("學習中")
} else if (state == 2) {
console.log("休學中")
} else if (state == 3) {
console.log("已過期")
} esle if (state ==4){
console.log("未購買")
}
switch (state) {
case 0:
break
case 1:
break
case 2:
break
case 3:
break
case 4:
break
}
向上面這種情況使用switch更好, 假設(shè)state為4,那么if-else語句就要進行4次判定,switch只要進行一次即可。
但是有的情況下switch也做不到if-else的事情, 例如有多個判斷條件的情況下,無法使用switch
6.3 布局上使用flexbox
在早期的 CSS 布局方式中我們能對元素實行絕對定位、相對定位或浮動定位。而現(xiàn)在,我們有了新的布局方式 flexbox,它比起早期的布局方式來說有個優(yōu)勢,那就是性能比較好。
關(guān)于前端性能優(yōu)化就寫到這里了,相信還有很多在代碼細節(jié)上注意就能進行性能優(yōu)化的點,大家可以到公眾號【程序員成長指北】后臺留言, 后期也可以繼續(xù)完善文章,謝謝
參考文章:
https://juejin.cn/post/6844903506906710024#comment
https://learnku.com/docs/f2e-performance-rules/reduce-the-number-of-http-requests/6369
