摘要
使用lighthouse進(jìn)行性能檢測,并對lighthouse提出的建議進(jìn)行優(yōu)化。
lighthouse
chrome的插件用不了,下了一個本地的。
## 下載
npm install -g lighthouse
## 運(yùn)行
lighthouse 127.0.0.1:8080 --view --emulated-form-factor desktop -throttling-method=provided
主要優(yōu)化
靜態(tài)化
服務(wù)端渲染,“直出”頁面,具有較好的SEO和首屏加載速度。主要還有以下的優(yōu)點(diǎn):
- 使用jsp模板語法(百度后發(fā)現(xiàn)是用Velocity模板語法)渲染頁面,減少了js文件體積
- 減少了請求數(shù)量
- 因?yàn)椴挥玫却罅拷涌诜祷?,加快了首屏?xí)r間
可以嘗試Vue的服務(wù)端渲染。首頁目前有部分是用接口讀取數(shù)據(jù),然后用jq進(jìn)行渲染,性能上應(yīng)該不如Virtual DOM,不過內(nèi)容不多。
圖片懶加載
這是一個很重要的優(yōu)化項(xiàng)。因?yàn)楣倬W(wǎng)上有很多圖片,而且編輯們上傳文章圖片的時候一般沒有壓縮,但是很多圖片的體積都很大。還有一個輪播圖,20張圖標(biāo),最小的幾十K,最大的兩百多K。對于圖片來源不可控的頁面,懶加載是個很實(shí)用的操作,直接將首屏加載的資源大小加少了十幾M。
圖片壓縮
對于來源可控,小圖標(biāo)等圖片可以用雪碧圖,base64等方法進(jìn)行優(yōu)化。目前只是用工具壓縮了圖片大小,后續(xù)可以考慮在webpack打包的時候生成雪碧圖。
異步加載js
通過<script>標(biāo)簽引入的js文件,可以設(shè)置defer,async屬性讓其異步加載,而不會阻塞渲染。defer和async的區(qū)別在于async加載完就立即執(zhí)行,沒有考慮依賴,標(biāo)簽順序等。而defer加載完后會等它前面引入的文件執(zhí)行完再執(zhí)行。一般defer用的比較多,async只能用在那些跟別的文件沒有聯(lián)系的孤兒腳本上。
因?yàn)轫?xiàng)目還是用webpack打包的,打包結(jié)果會將js文件以<script>標(biāo)簽的形式注入到html模板中,那要如何給這個標(biāo)簽加上defer屬性呢?通過搜索,發(fā)現(xiàn)了webpack有一個html-webpack-plugin插件的擴(kuò)展插件script-ext-html-webpack-plugin,可以通過這個插件給注入到html中的script標(biāo)簽加上defer屬性。
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
plugins.push(new ScriptExtHtmlWebpackPlugin({
defaultAttribute: 'defer'
}))
異步加載css
沒想到css也能異步加載,但這是lighthouse給出的建議。找了一下發(fā)現(xiàn)有以下兩種方法:
一是通過js腳本在文檔中插入<link>標(biāo)簽
二是通過<link>的media屬性
media屬性是媒體查詢用的,用于在不同情況下加載不同的css。這里是將其設(shè)置為一個不適配當(dāng)前瀏覽器環(huán)境的值,甚至是不能識別的值,瀏覽器會認(rèn)為這個樣式文件優(yōu)先級低,會在不阻塞的情況加載。加載完成后再將media設(shè)置為正常值,讓瀏覽器解析css。
<link rel="stylesheet" media="none" onload="this.media='all'">
這里用的是第二種方法。但是webpack注入到html中的外鏈css還沒找到異步加載的方法。
preconnent
lighthouse建議對于接下來會訪問的地址可以提前建立連接。一般有一下幾種方式。
dns-prefetch
域名預(yù)解析
<link rel="dns-prefetch" >
preconnet
預(yù)連接
<link rel="preconnect" >
<link rel="preconnect" crossorigin>
prefetch
預(yù)加載
<link rel="prefetch" as="html" crossorigin="use-credentials">
<link rel="prefetch" href="library.js" as="script">
prerender
預(yù)渲染
<link rel="prerender" >
這四種層層遞進(jìn),但是不要連接不需要的資源,反而損耗性能。我在頁面上對某些資源用了preconnect,但并沒有明顯的效果。應(yīng)該對于在線小說,在線漫畫這種場景預(yù)加載會更適用。
代碼優(yōu)化
lighthouse上顯示主線程耗時最多的是樣式和布局,所以對這部分進(jìn)行優(yōu)化。主要有一下幾點(diǎn):
- 去掉頁面上用于布局的table,table本身性能較低,且維護(hù)性差,是一種過時的布局方案。
- 在去掉table布局的同時減少一些無意義的DOM元素,減少DOM元素的數(shù)量和嵌套。
- 減少css選擇器的嵌套。用sass,less這種css預(yù)處理器很容易造成多層嵌套。優(yōu)化前代碼里最多的有七八層嵌套,對性能有一定影響。重構(gòu)后不超過三層。
通過上面的重構(gòu)后,樣式布局和渲染時間從lighthouse上看大概減少了300ms。但樣式和布局的時間還是最長的,感覺還有優(yōu)化空間。
接下來是js代碼的優(yōu)化和重構(gòu)。因?yàn)橐瞥齎ue框架,以及用服務(wù)端端直出,現(xiàn)在js代碼已經(jīng)減少了大部分。主要有以下幾部分:
- 拆分函數(shù),將功能復(fù)雜的函數(shù)拆分成小函數(shù),讓每個函數(shù)只做一件事。
- 優(yōu)化分支結(jié)構(gòu),用對象
Object,代替if...else和switch...case
如下面這段代碼,優(yōu)化后變得更加簡潔,也便于維護(hù)。
// 優(yōu)化前
var getState = function (state) {
switch (state) {
case 1:
return 'up';
case 0:
return 'stay';
case 2:
return 'down';
}
}
// 優(yōu)化后
var getState = function(state) {
var stateMap = {
1: 'up', 0: 'stay', 2: 'down'
}
return stateMap[state]
}
- 優(yōu)化DOM操作
DOM操作如改變樣式,改變內(nèi)容可能會引起頁面的重繪重排,是比較消耗性能的。網(wǎng)上也有很多優(yōu)化jq操作的方法。
如將查詢到的DOM使用變量存起來,避免重復(fù)查詢。以及將多次DOM操作變成一次等。這里重點(diǎn)講一下第二種。
常見的需求是渲染一個列表,如果直接在for循環(huán)里面append到父元素中,性能是非常差的。幸好原來的操作是將所有DOM用字符串拼接起來,再用html()方法一次性添加到頁面中。
還有另一種方法是使用文檔碎片(fragment)。通過document.createDocumentFragment()可以新建一個fragment。向fragment中appendChild元素的時候是不會阻塞渲染進(jìn)程的。最后將fragment替換掉頁面上的元素。將fragment元素用appendChild的方法添加到頁面上時,實(shí)際上添加上去的是它內(nèi)部的元素,也就是它的子元素。
var fragment = document.createDocumentFragment()
for (var i = 0; i < data.length; i++) {
var str = '<div>' + i + '</div>'
fragment.appendChild($(str)[0])
}
$('.container').append(fragment)
經(jīng)過測試,在當(dāng)前的場景下,使用fragment的速度和html()是差不多的,都是10ms左右。區(qū)別在于最后將fragment添加到頁面上$('.container').append(fragment)這行代碼僅僅花費(fèi)1ms。也就是說,將fragment插入頁面時不會引起頁面重繪重排,不會引起阻塞。
其他還沒做的優(yōu)化
lighthouse的提議還有以下幾點(diǎn):
- 使用新的圖片格式,如webp。以或得更高的壓縮率和更小圖片體積。但是新的圖片格式兼容性不是很好,故拋棄。
- 使用文本壓縮,如gzip。js,css和html文件都可以使用gzip壓縮,webpack也有對應(yīng)的配置項(xiàng)。有時候費(fèi)盡腦汁去考慮怎么減小代碼體積都不如gzip壓縮來的有效果。但這個需要在服務(wù)器配置。
總結(jié)
個人認(rèn)為,對首屏加載速度影響最大的還是資源大小,請求數(shù)量,請求速度等。代碼方面,前端一般很難寫出嚴(yán)重影響速度的代碼。減小資源大小,可以用各種壓縮,懶加載,預(yù)加載,異步加載等方法。減少請求數(shù)量可以使用雪碧圖,搭建node中臺將多個請求合并成一個等。對于官網(wǎng)這種項(xiàng)目,最好使用服務(wù)端渲染,首屏快之外,也有利于SEO。