思維導(dǎo)圖
一.npm run build 縮小打包的體積
首先來(lái)看我未作任何處理的一個(gè)打包
這個(gè)包很大硝桩,里面有很多文件走越。你會(huì)發(fā)現(xiàn)里面有很多.map結(jié)尾的文件,占據(jù)了非常大的空間坷随,下面我們一步一步來(lái):
1.productionSourceMap
Type: boolean
Default: true
用途:
設(shè)置生產(chǎn)環(huán)境的 source map 開(kāi)啟與關(guān)閉饱岸。
用法
module.exports = {
publicPath: './', // 基本路徑
outputDir: 'dist', // 輸出文件目錄
assetsDir: './assets',
indexPath: 'index.html',
filenameHashing: true, // 生成的靜態(tài)資源在它們的文件名中包含了 hash 以便更好的控制緩存
lintOnSave: false, // eslint-loader 是否在保存的時(shí)候檢查
productionSourceMap: true, // 生產(chǎn)環(huán)境是否生成 sourceMap 文件
}
source map 直譯過(guò)來(lái)就是資源地圖。所以载慈,source map的作用就是定位惭等。source map定位的時(shí)瀏覽器控制臺(tái)輸出語(yǔ)句在項(xiàng)目文件的位置。
好的办铡,加下來(lái)我們把productionSourceMap改為false辞做,看看效果:
發(fā)現(xiàn).map文件都沒(méi)有了,進(jìn)入下一步:
webpack-bundle-analyzer
這是一款用戶(hù)分析打包大小的插件寡具,使用如下:
cnpm install webpack-bundle-analyzer --s-d
在vue-cli3.0項(xiàng)目中,新建vue.config.js中
module.exports = {
productionSourceMap: false,
chainWebpack:config=>{
config.plugin('webpack-bundle-analyzer').use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
}
}
在vue-cli2.x項(xiàng)目中凭豪,在build/webpack.prod.config.js中的module.export - webpackConfig前面加上:
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
執(zhí)行:
npm run build --report
就可以看到:
fast 3G的情況下加載了17s,可以看到vendor.js這個(gè)文件有1.84m晒杈,里面echarts和elementui這兩個(gè)庫(kù)占了大頭嫂伞。address.json這個(gè)是地理位置的json,最好是可以改成后臺(tái)獲取拯钻,以減小打包文件體積帖努,這里暫時(shí)不表。先來(lái)看看怎么處理那兩個(gè)大頭粪般。
有兩個(gè)方向:
1.按需加載
本來(lái)直接在Main.js中:
import echarts from 'echarts'
Vue.prototype.$echarts = echarts
現(xiàn)在改為在echarts組件里引入
var echarts = require('echarts/lib/echarts');
require('echarts/lib/chart/line') // 按需導(dǎo)入折線(xiàn)組件
require('echarts/lib/component/tooltip') // 提示組件
require('echarts/lib/component/legend') // 圖例組件
2.使用cdn托管
大型Web應(yīng)用對(duì)速度的追求并沒(méi)有止步于僅僅利用瀏覽器緩存拼余,因?yàn)闉g覽器緩存始終只是為了提升二次訪(fǎng)問(wèn)的速度,對(duì)于首次訪(fǎng)問(wèn)的加速亩歹,我們需要從網(wǎng)絡(luò)層面進(jìn)行優(yōu)化匙监,最常見(jiàn)的手段就是CDN(Content Delivery Network凡橱,內(nèi)容分發(fā)網(wǎng)絡(luò))加速。通過(guò)將靜態(tài)資源緩存到離用戶(hù)很近的相同網(wǎng)絡(luò)運(yùn)營(yíng)商的CDN節(jié)點(diǎn)上亭姥,不但能提升用戶(hù)的訪(fǎng)問(wèn)速度稼钩,還能節(jié)省服務(wù)器的帶寬消耗,降低負(fù)載达罗。不同地區(qū)的用戶(hù)會(huì)訪(fǎng)問(wèn)到離自己最近的相同網(wǎng)絡(luò)線(xiàn)路上的CDN節(jié)點(diǎn)坝撑,當(dāng)請(qǐng)求達(dá)到CDN節(jié)點(diǎn)后,節(jié)點(diǎn)會(huì)判斷自己的內(nèi)容緩存是否有效粮揉,如果有效巡李,則立即響應(yīng)緩存內(nèi)容給用戶(hù),從而加快響應(yīng)速度扶认。如果CDN節(jié)點(diǎn)的緩存失效侨拦,它會(huì)根據(jù)服務(wù)配置去我們的內(nèi)容源服務(wù)器獲取最新的資源響應(yīng)給用戶(hù),并將內(nèi)容緩存下來(lái)以便響應(yīng)給后續(xù)訪(fǎng)問(wèn)的用戶(hù)辐宾。因此阳谍,一個(gè)地區(qū)內(nèi)只要有一個(gè)用戶(hù)先加載資源,在CDN中建立了緩存螃概,該地區(qū)的其他后續(xù)用戶(hù)都能因此而受益。
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"/>
<meta http-equiv="pragram" content="no-cache">
<meta http-equiv="cache-control" content="no-cache, must-revalidate">
<meta http-equiv="expires" content="0">
<title>chitic-sems-app</title>
<link rel="stylesheet" ></head>
<% for (var i in htmlWebpackPlugin.options.css) { %>
<link href="<%= htmlWebpackPlugin.options.css[i] %>" rel="stylesheet">
<% } %>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
<% for (var i in htmlWebpackPlugin.options.js) { %>
<script src="<%= htmlWebpackPlugin.options.js[i] %>"></script>
<% } %>
</body>
</html>
這里有個(gè)坑:
1.不要把任何頁(yè)面內(nèi)容寫(xiě)到 body鸽疾、head 外面。
2.app.js 插入到了你寫(xiě)的 script 的前面,獲取不到 Vue尺栖。
JS部分
vue-cli2.X項(xiàng)目
首先在config/index.js文件中:
在dev和build內(nèi)部加入:
cdn: {
css:[],
js: [
'https://cdn.staticfile.org/vue/2.6.10/vue.min.js',
'https://cdn.staticfile.org/vuex/3.0.1/vuex.min.js',
'https://cdn.staticfile.org/vue-router/3.0.3/vue-router.min.js',
'https://cdn.bootcss.com/element-ui/2.13.2/index.js',
'https://cdn.bootcss.com/echarts/4.2.1/echarts.simple.min.js'
]
}
dev用于開(kāi)發(fā)環(huán)境碳胳,build用于開(kāi)發(fā)環(huán)境的資源請(qǐng)求。
然后在webpack.base.conf.js的module.export{}內(nèi)部加入
externals: {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'element-ui':'ELEMENT',
'echarts':'echarts'
}
externals的目的就是將不怎么需要更新的第三方庫(kù)脫離webpack打包豺鼻,不被打入bundle中综液,從而減少打包時(shí)間,但又不影響運(yùn)用第三方庫(kù)的方式
下一步在webpack.dev.conf.js中利用HtmlWebpackPlugin注入資源
new HtmlWebpackPlugin(Object.assign(
{
filename: 'index.html',
template: 'index.html',
inject: true
},
config.dev.cdn
)),
此時(shí)你開(kāi)發(fā)環(huán)境下已經(jīng)引入了這些資源了儒飒。
下一步在webpack.prod.conf.js中利用HtmlWebpackPlugin注入資源
new HtmlWebpackPlugin(Object.assign({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
},
config.build.cdn)),
大功告成谬莹,讓我們運(yùn)行一下npm run build --report
當(dāng)把a(bǔ)ddress.json也改成接口之后,我們?cè)賮?lái)看看打包的大小
vue-cli3.0項(xiàng)目
新建vue.config.js
// 代碼壓縮
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const CompressionWebpackPlugin = require('compression-webpack-plugin')
// 是否為生產(chǎn)環(huán)境
const isProduction = process.env.NODE_ENV !== 'development'
// 本地環(huán)境是否需要使用cdn
const devNeedCdn = true
// cdn鏈接
const cdn = {
// cdn:模塊名稱(chēng)和模塊作用域命名(對(duì)應(yīng)window里面掛載的變量名稱(chēng))
externals: {
vue: 'Vue',
vuex: 'Vuex',
'vue-router': 'VueRouter',
'element-ui':'ELEMENT'
},
// cdn的css鏈接
css: [],
// cdn的js鏈接
js: [
'https://cdn.staticfile.org/vue/2.6.10/vue.min.js',
'https://cdn.staticfile.org/vuex/3.0.1/vuex.min.js',
'https://cdn.staticfile.org/vue-router/3.0.3/vue-router.min.js',
'https://cdn.bootcss.com/element-ui/2.13.2/index.js'
]
}
let path = require('path')
module.exports = {
productionSourceMap: false, //優(yōu)化打包體積
publicPath: './',
lintOnSave: false,
chainWebpack:config =>{
// ============注入cdn start============
config.plugin('html').tap(args => {
// 生產(chǎn)環(huán)境或本地需要cdn時(shí)桩了,才注入cdn
if (isProduction || devNeedCdn) args[0].cdn = cdn
return args
})
config.plugin('webpack-bundle-analyzer').use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
// ============注入cdn start============
},
configureWebpack: config => {
// 用cdn方式引入附帽,則構(gòu)建時(shí)要忽略相關(guān)資源
if (isProduction || devNeedCdn) config.externals = cdn.externals
// 生產(chǎn)環(huán)境相關(guān)配置
if (isProduction) {
// 代碼壓縮
config.plugins.push(
new UglifyJsPlugin({
uglifyOptions: {
//生產(chǎn)環(huán)境自動(dòng)刪除console
compress: {
// warnings: false, // 若打包錯(cuò)誤,則注釋這行
drop_debugger: true,
drop_console: true,
pure_funcs: ['console.log']
}
},
sourceMap: false,
parallel: true
})
)
}
// 生產(chǎn)環(huán)境相關(guān)配置
}
}
來(lái) 運(yùn)行打包
可以看到原來(lái)1.84m的文件井誉,現(xiàn)在只剩下了250k,讓我們情理緩存之后再來(lái)看一下加載速度:
可以看到從17s到了9s 縮短了一半蕉扮,接下來(lái)還有什么辦法呢,我們繼續(xù)下一步
Gzip壓縮
gzip壓縮可以提高2-3倍的速度颗圣,非常棒喳钟。讓我們來(lái)操作一下
選擇的項(xiàng)目比較老屁使,來(lái)講一下
vue-cli2.X的
下載compression-webpack-plugin這個(gè)plugin
在你的webpack.prod.conf.js文件里添加以下代碼:(與webpackConfig平級(jí))
//開(kāi)啟gzip壓縮
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
再將config文件夾下的index.js中的productionGzip改為true即可,如果報(bào)錯(cuò)
說(shuō)明你的plugin版本太高了奔则,降低版本即可
npm install --save-dev compression-webpack-plugin@1.1.12
vue-cli3.0的
在vue.config.js內(nèi)加入
// gzip壓縮
const productionGzipExtensions = ['html', 'js', 'css']
config.plugins.push(
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' + productionGzipExtensions.join('|') + ')$'
),
threshold: 10240, // 只有大小大于該值的資源會(huì)被處理 10240
minRatio: 0.8, // 只有壓縮率小于這個(gè)值的資源才會(huì)被處理
deleteOriginalAssets: false // 刪除原文件
})
)
}
此時(shí)我們來(lái)執(zhí)行打包之后就可以看到
超過(guò)10k的文件都出現(xiàn)了.gz結(jié)尾的壓縮包蛮寂,體積只有原來(lái)的1/3-1/2。如果資源請(qǐng)求這些壓縮包的話(huà)应狱,加載速度還會(huì)提升一個(gè)檔次共郭。當(dāng)然光是前端這樣做還不夠,還需要配置nginx疾呻。這里還有一個(gè)小技巧除嘹,在webpack.prod.conf.js的UglifyJsPlugin中添加一下一句話(huà),可以去掉生產(chǎn)環(huán)境里的console.log
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false,
// =====以下是新增的=====
drop_console: true, // 刪除頁(yè)面中的 console.log
pure_funcs: ['console.log']
// =====以上是新增的=====
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
下面我們來(lái)講講nginx的配置
在nginx.conf內(nèi)添加如下代碼:
gzip on;
gzip_min_length 1k;
gzip_comp_level 9;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml
text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
整體配置如下
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
upstream njsolar {
server localhost:9000; #Apache
}
server {
listen 8083;
server_name localhost;
gzip on;
gzip_min_length 1k;
gzip_comp_level 9;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root /usr/local/deploy/njsolar-ui/dist;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
}
}
此時(shí)我們啟動(dòng)nginx:./nginx.exe,我們會(huì)發(fā)現(xiàn):
我們成功請(qǐng)求到了gzip文件岸蜗,vender文件從最初的1.8M縮減到了現(xiàn)在的75kb尉咕,請(qǐng)求時(shí)間也從17s縮減到了7s,如果從fast3G切換成online璃岳,基本上加載時(shí)間就在一秒左右年缎,取得巨大進(jìn)步。
如果還要再進(jìn)一步的話(huà)铃慷,那就可以用SSR和骨架屏的方案单芜,比較復(fù)雜,這里不加贅述犁柜。vue可以用nuxt.js框架開(kāi)發(fā)洲鸠。
首屏加載與打包的優(yōu)化講完了,接下來(lái)我們來(lái)講一講
Vue代碼優(yōu)化
1.路由懶加載
未用懶加載馋缅,vue中路由代碼如下
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component:HelloWorld
}
]
})
vue異步組件實(shí)現(xiàn)懶加載
方法如下:component:resolve=>(require(['需要加載的路由的地址'])扒腕,resolve)
import Vue from 'vue'
import Router from 'vue-router'
/* 此處省去之前導(dǎo)入的HelloWorld模塊 */
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: resolve=>(require(["@/components/HelloWorld"],resolve))
}
]
})
ES 提出的import方法,(------最常用------)
方法如下:const HelloWorld = ()=>import('需要加載的模塊地址')(不加 { } 萤悴,表示直接return)
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const HelloWorld = ()=>import("@/components/HelloWorld")
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component:HelloWorld
}
]
})
2.組件懶加載
原來(lái)組件中寫(xiě)法
<template>
<div class="hello">
<One-com></One-com>
1111
</div>
</template>
<script>
import One from './one'
export default {
components:{
"One-com":One
},
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
組件懶加載寫(xiě)法:
<template>
<div class="hello">
<One-com></One-com>
1111
</div>
</template>
<script>
export default {
components:{
"One-com":resolve=>(['./one'],resolve)
},
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
3.http連接優(yōu)化
HTTP協(xié)議定義Web客戶(hù)端如何從服務(wù)器請(qǐng)求web頁(yè)面瘾腰,以及服務(wù)器如何把web頁(yè)面?zhèn)魉徒o客戶(hù)端,瀏覽器請(qǐng)求獲取數(shù)據(jù)通過(guò)http連接完成覆履,因此蹋盆,優(yōu)化http連接優(yōu)化是web項(xiàng)目?jī)?yōu)化的重要方向之一。Web項(xiàng)目的由瀏覽器呈現(xiàn)硝全,下面我們來(lái)看看瀏覽器對(duì)請(qǐng)求的處理過(guò)程
附上幾份緩存相關(guān)的教程:
https://www.cnblogs.com/fangsmile/p/13072940.html
https://blog.csdn.net/lianxin19900610/article/details/82417233
https://mp.weixin.qq.com/s/FVNlCZhOpJk5gUCnhsmSzQ
3.1 減少HTTP請(qǐng)求
3.1.1 合并CSS怪嫌、js、圖片柳沙。將css岩灭、js、圖片合并赂鲤,可以有效減少http請(qǐng)求數(shù)噪径;
3.1.2 合理規(guī)劃API柱恤,將可合并的接口請(qǐng)求合并。瀏覽器對(duì)并發(fā)的http請(qǐng)求數(shù)量有限制找爱,即使API響應(yīng)很快梗顺,在多個(gè)接口并發(fā)請(qǐng)求時(shí),仍會(huì)在瀏覽器中造成不同程度的卡頓车摄;
3.2合理使用緩存
合理設(shè)置HTTP緩存寺谤。從上圖瀏覽器請(qǐng)求處理流程圖中可以看出,恰當(dāng)?shù)木彺嬖O(shè)置可以大大減少HTTP請(qǐng)求吮播,節(jié)省帶寬变屁。如很少變化的資源(html、css意狠、js粟关、圖片等)通過(guò) HTTP Header中的cache-control和Expires可設(shè)定瀏覽器緩存,變化不頻繁又可能變的資源使用Last-Modifed來(lái)做請(qǐng)求驗(yàn)證环戈。
3.3使用字體圖標(biāo)闷板,少用圖片
使用字體圖標(biāo)的優(yōu)點(diǎn):
3.3.1輕量:一個(gè)圖標(biāo)字體比一系列的圖像(特別是在Retina屏中使用雙倍圖像)要小。一旦圖標(biāo)字體加載了院塞,圖標(biāo)就會(huì)馬上渲染出來(lái)遮晚,不需要下載一個(gè)圖像±怪梗可以減少HTTP請(qǐng)求县遣,還可以配合HTML5離線(xiàn)存儲(chǔ)做性能優(yōu)化;
3.3.2靈活性:圖標(biāo)字體可以用過(guò)font-size屬性設(shè)置其任何大小创泄,還可以加各種文字效果,包括顏色括蝠、Hover狀態(tài)鞠抑、透明度、陰影和翻轉(zhuǎn)等效果忌警「樽荆可以在任何背景下顯示。
3.3.3兼容性:網(wǎng)頁(yè)字體支持所有現(xiàn)代瀏覽器法绵,包括IE低版本箕速。
3.4圖片懶加載
在實(shí)際的項(xiàng)目開(kāi)發(fā)中,我們通常會(huì)遇見(jiàn)這樣的場(chǎng)景:一個(gè)頁(yè)面有很多圖片朋譬,而首屏出現(xiàn)的圖片大概就一兩張盐茎,那么我們還要一次性把所有圖片都加載出來(lái)嗎?顯然這是愚蠢的徙赢,不僅影響頁(yè)面渲染速度字柠,還浪費(fèi)帶寬探越。這也就是們通常所說(shuō)的首屏加載,技術(shù)上現(xiàn)實(shí)其中要用的技術(shù)就是圖片懶加載--到可視區(qū)域再加載窑业。
4.其他小技巧
4.1盡量提取公共方法和公共組件钦幔。
4.1.1提取公共組件,盡量與業(yè)務(wù)隔離常柄,如篩選條件等鲤氢;
4.1.2提取公共方法,放在util.js中西潘,如表單校驗(yàn)封裝卷玉;
4.1.3提取在項(xiàng)目中出現(xiàn)較多的功能模塊,如彈窗詳情等秸架;
4.2 v-for和v-if
在vue中v-for和v-if不要放在同一個(gè)元素上使用揍庄。由于 v-for 和 v-if 放在同一個(gè)元素上使用會(huì)帶來(lái)一些性能上的影響,而且v-for時(shí)要加上key东抹,便于虛擬dom的遍歷與渲染,當(dāng)舊值和新值去對(duì)比的時(shí)候蚂子,可以更快的定位到diff
4.3 Keep-alive
一些不太需要刷新的頁(yè)面,內(nèi)容又比較復(fù)雜的缭黔,建議用Keep-alive食茎。Keep-alive會(huì)新增兩個(gè)生命鉤子函數(shù)。
4.4 created()和mouted()
一些異步請(qǐng)求盡量放在mouted里面馏谨,這樣不會(huì)導(dǎo)致因?yàn)檎?qǐng)求過(guò)久讓頁(yè)面長(zhǎng)時(shí)間白屏别渔。
4.5 treesharking
webpack會(huì)自動(dòng)在生產(chǎn)環(huán)境下(mode:production)進(jìn)行treesharking,前提是依賴(lài)必須通過(guò)es6的方式引入:import.
4.6 避免回流和重繪
回流(Reflow)
當(dāng) Render Tree 中部分或全部元素的尺寸、結(jié)構(gòu)惧互、或某些屬性發(fā)生改變時(shí)哎媚,瀏覽器重新渲染部分或全部文檔的過(guò)程稱(chēng)為回流。
會(huì)導(dǎo)致回流的事件:
- 頁(yè)面首次渲染
- 瀏覽器窗口大小發(fā)生改變
- 元素尺寸或位置發(fā)生改變?cè)貎?nèi)容變化(文字?jǐn)?shù)量或圖片大小等等)
- 元素字體大小變化
- 添加或者刪除可見(jiàn)的 DOM 元素
- 激活 CSS 偽類(lèi)(例如:hover)
- 查詢(xún)某些屬性或調(diào)用某些方法
- 一些常用且會(huì)導(dǎo)致回流的屬性和方法
clientWidth喊儡、clientHeight拨与、clientTop、clientLeftoffsetWidth艾猜、offsetHeight买喧、offsetTop、offsetLeftscrollWidth匆赃、scrollHeight淤毛、scrollTop、scrollLeftscrollIntoView()算柳、scrollIntoViewIfNeeded()低淡、getComputedStyle()、
getBoundingClientRect()、scrollTo()
重繪(Repaint)
當(dāng)頁(yè)面中元素樣式的改變并不影響它在文檔流中的位置時(shí)(例如:color查牌、background-color事期、visibility 等),瀏覽器會(huì)將新樣式賦予給元素并重新繪制它纸颜,這個(gè)過(guò)程稱(chēng)為重繪兽泣。
如何避免
CSS
1.避免使用 table 布局。
2.盡可能在 DOM 樹(shù)的最末端改變 class胁孙。
3.避免設(shè)置多層內(nèi)聯(lián)樣式唠倦。
4.將動(dòng)畫(huà)效果應(yīng)用到 position 屬性為 absolute 或 fixed 的元素上。
5.避免使用 CSS 表達(dá)式(例如:calc())涮较。
Javascript
1.避免頻繁操作樣式稠鼻,最好一次性重寫(xiě) style 屬性,或者將樣式列表定義為 class 并一次性更改 class 屬性狂票。
// 優(yōu)化前
const el = document.getElementById('test');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
// 優(yōu)化后,一次性修改樣式候齿,這樣可以將三次重排減少到一次重排
const el = document.getElementById('test');
el.style.cssText += '; border-left: 1px ;border-right: 2px; padding: 5px;'
2.避免頻繁操作 DOM,創(chuàng)建一個(gè) documentFragment闺属,在它上面應(yīng)用所有 DOM 操作慌盯,最后再把它添加到文檔中。
3.也可以先為元素設(shè)置 display: none掂器,操作結(jié)束后再把它顯示出來(lái)亚皂。因?yàn)樵?display 屬性為 none 的元素上進(jìn)行的 DOM 操作不會(huì)引發(fā)回流和重繪。
4.避免頻繁讀取會(huì)引發(fā)回流/重繪的屬性国瓮,如果確實(shí)需要多次使用灭必,就用一個(gè)變量緩存起來(lái)。
5.對(duì)具有復(fù)雜動(dòng)畫(huà)的元素使用絕對(duì)定位乃摹,使它脫離文檔流禁漓,否則會(huì)引起父元素及后續(xù)元素頻繁回流。
4.7 防抖與節(jié)流
防抖debounce
監(jiān)聽(tīng)一個(gè)input輸入框的keyup,但是不能一松開(kāi)鍵盤(pán)就觸發(fā)事件孵睬,因?yàn)榭赡軙?huì)頻繁調(diào)用接口播歼,應(yīng)該松開(kāi)若干毫秒后不再按下,才開(kāi)始調(diào)用接口
const input1 = document.getElementById('input')
var timer = null
input1.addEventListener('keyup',function(){
//假如輸入123肪康,首先輸入1的時(shí)候荚恶,time是null撩穿,觸發(fā)定時(shí)器磷支,
// 500毫秒之后打印,但是在500毫秒之內(nèi)再次輸入了2食寡,觸發(fā)keyup雾狈,此時(shí)time!=null
// 就清空了原來(lái)應(yīng)該打印1的定時(shí)器抵皱,重新執(zhí)行一個(gè)新的定時(shí)器打印12善榛,關(guān)鍵在于清空原來(lái)的定時(shí)器
if(timer){
clearTimeout(timer)
}
timer = setTimeout(() => {
console.log(input1.value)
timer = null
}, 500);
})
// 封裝一個(gè)方法
function debounce(fn,delay = 500){
// timer在閉包中
let timer =null
return function(){
if(timer){
clearTimeout(timer)
}
timer = setTimeout(() => {
fn()
// fn.apply(this,arguments) 用這種更完美
timer = null
}, delay);
}
}
input1.addEventListener('keyup',debounce(()=>{
console.log(input1.value)
},1000))
節(jié)流throttle
拖拽一個(gè)元素時(shí)辩蛋,要隨時(shí)拿到該元素被拖拽的位置,直接用drag事件移盆,則會(huì)頻繁觸發(fā)悼院,很容易導(dǎo)致卡頓。 節(jié)流:無(wú)論拖拽速度多快咒循,都會(huì)每隔100ms觸發(fā)一次
const div1 = document.getElementById('div1')
var timer = null
div1.addEventListener('drag',function(e){
//存在timer則說(shuō)明前一次還沒(méi)執(zhí)行完据途,必須前一次執(zhí)行完,才能執(zhí)行下一次操作叙甸,確保規(guī)定時(shí)間只執(zhí)行一次颖医,
// 和防抖的區(qū)別在于,防抖是清空原來(lái)執(zhí)行新的裆蒸,節(jié)流是執(zhí)行原來(lái)的熔萧,正好相反
if(timer){
return
}
timer = setTimeout(() => {
console.log(e.offsetX,e.offsetY)
timer = null
}, 500);
})
// 封裝一個(gè)方法
function throttle(fn,delay = 500){
// timer在閉包中
let timer =null
return function(){
if(timer){
return
}
timer = setTimeout(() => {
console.log(this) //this是div1,箭頭函數(shù)承接上文,就是return的方法
fn.apply(this,arguments) //只是為了綁定事件的參數(shù),fn.apply({},arguments)也可以起到效果
timer = null
}, delay);
}
}
div1.addEventListener('drag',throttle((e)=>{
console.log(this) //this是window 箭頭函數(shù)承接上文僚祷,就是window
console.log(e.offsetX,e.offsetY)
},1000))
div1.addEventListener('drag',throttle(function(e){
console.log(this) //this是div
console.log(e.offsetX,e.offsetY)
},1000))
性能監(jiān)控
window.performance.timing
以下給出統(tǒng)計(jì)頁(yè)面性能指標(biāo)的方法佛致。
let times = {};
let t = window.performance.timing;
// 優(yōu)先使用 navigation v2 https://www.w3.org/TR/navigation-timing-2/
if (typeof win.PerformanceNavigationTiming === 'function') {
try {
var nt2Timing = performance.getEntriesByType('navigation')[0]
if (nt2Timing) {
t = nt2Timing
}
} catch (err) {
}
}
//重定向時(shí)間
times.redirectTime = t.redirectEnd - t.redirectStart;
//dns 查詢(xún)耗時(shí)
times.dnsTime = t.domainLookupEnd - t.domainLookupStart;
//TTFB 讀取頁(yè)面第一個(gè)字節(jié)的時(shí)間
times.ttfbTime = t.responseStart - t.navigationStart;
//DNS 緩存時(shí)間
times.appcacheTime = t.domainLookupStart - t.fetchStart;
//卸載頁(yè)面的時(shí)間
times.unloadTime = t.unloadEventEnd - t.unloadEventStart;
//tcp 連接耗時(shí)
times.tcpTime = t.connectEnd - t.connectStart;
//request 請(qǐng)求耗時(shí)
times.reqTime = t.responseEnd - t.responseStart;
//解析 dom 樹(shù)耗時(shí)
times.analysisTime = t.domComplete - t.domInteractive;
//白屏?xí)r間
times.blankTime = (t.domInteractive || t.domLoading) - t.fetchStart;
//domReadyTime
times.domReadyTime = t.domContentLoadedEventEnd - t.fetchStart;
當(dāng)然現(xiàn)在如果要做性能監(jiān)控的話(huà),一般都不用自己寫(xiě)久妆,git上有大把的開(kāi)源項(xiàng)目給你選擇晌杰。
這篇性能優(yōu)化的文章寫(xiě)的也很不錯(cuò)哦:https://segmentfault.com/a/1190000019185648