前端性能优化(总结)
前言
作为一个有梦想的前端工作人员,我们不能满足于功能的完整,页面的美观,没有性能极佳的性能,自然也不会让用户眼前一亮。从用户发出请求,到呈现页面,数据经历了一个“漫长”的过程。因此,有必要先了解一下,从输入URL到页面加载完成,都发生了什么?
1、用户输入URL
2、浏览器通过DNS,把url解析为IP
3、和IP地址建立链接,发送HTTP请求
4、服务器接收请求,查库,读文件等,拼接好返回的HTTP响应
5、浏览器收到首屏HTML,开始渲染
6、解析html为dom
7、解析css为css-tree
8、dom+css生成render-tree绘图
9、加载script的js文件
10、执行js
所谓性能优化,就是在数据经历的每个步骤中,时间尽可能的短,所以基本有两个大方向。
1、少加载文件
1)文件打包压缩
2)图片格式和图片压缩
3)缓存
4)cdn(缩短距离,提高加载效率)
5)ssr(首页文件执行顺序)
6)lazy-load 懒加载
2、少执行代码
1)大部分优化策略集中在vue或react内部
2)长列表(本质上是对内存做优化)
1>移动端一些机型,比如淘宝首页的无线滚动,如果直接渲染,dom过多,会崩掉,内存存储数据过多,也会崩掉。
DNS
1、查看dns缓存
2、本地没缓存,发起dns请求,向本地配置的DNS服务器发请求(递归)
优化策略:prefetch预获取,比如使用了cdn的域名
<link rel="dns-prefetch" fref="//xxx.com"></link>
TCP
IP TCP HTTP的关系
网络协议就是让机器聊天
1、IP协议负责寻址
2、TCP协议负责数据完整性和有序性,三次握手,慢启动,重发,粘包,滑动窗口等机制
3、HTTP应用层,负责应用层数据,数据终止时机
优化策略:
1、长连接
2、减少文件体积
1)js打包压缩
2)图片压缩
3)gzip
3、减少文件请求次数
1)雪碧图
2)js,css打包
3)缓存机制
4)懒加载
4、减少用户和服务器的距离——cdn
5、本地存储
粘包
TCP有一个数据缓冲的策略。。。
HTTP2
二进制分帧
服务端推送
多路复用
缓存
1、强缓存
1)缓存数据生效的情况下,不需要和服务器交互,直接使用;
2、弱缓存
1)缓存数据时效了,需要问服务器,还能不能使用缓存。
浏览器缓存机制
广义的缓存,可以分为以下四点:
1、http cache(图片待上传)
1)Expires和Cache-Control两个header来控制强缓存
2)如果命中强缓存,就不会和服务器交互了,直接用缓存
3)如果强缓存失效了,需要执行协议缓存
4)询问文件是否修改,如果没有修改过,直接用缓存
5)如果有etag类似文件的指纹,这个优先级更高,因为更准确
2、service worker cache
1)Service Worker是一种独立于主线程之外的JavaScript线程。它脱离于浏览器窗体,可以实现离线缓存,网络代理等。
window.navigator.serviceWorker.register('/wbl.js').then(
function(){
console.log('注册成功')
}) .catch(err => {
console.log('注册失败')
})
3、memory cache
1)内存缓存,
4、push cache
1)http2的缓存
前端缓存最佳实践
<script src="http://xx.com/xx.js"></script>
1、html使用nocache
2、文件加指纹,并且修改文件名
3、静态资源都在cdn,专门的cdn域名
1)cdn缩减用户和服务器的距离,提升加载效率
2)浏览器对一个域名的并发数有限制,所以用cdn域名专门加载静态资源
4、html和js,css要分开上线
1)先上哪一个
2)先上html,加载的js如果有缓存,使用老的js报错
3)先上js,如果加载的js缓存失效,老的html加载了新的js报错
4)js由于内容修改了,就生成新文件,
5)先上静态资源(缓存设置时间比较长,有变化就是新文件,缺点是文件冗余)
6)后上html(不设置强缓存)
cdn的内部原理
三次握手
图片优化
1、jpg
1)有损压缩
2)体积小,不支持透明
3)用于背景图,轮播图
2、png
1)无损压缩,质量高,支持透明
2)色拍线条更丰富,小图,比如logo,商品icon
3)svg
1)文本,体积小,矢量图
2)渲染成本,学习成本
图片打包雪碧图,减少http请求次数,webpack-spritesmith
gzip
accept-encoding:gzip开启gzip
本地存储
1、cookie
1)最早,体积限定,性能浪费 ,所有请求都带上所有当前域名的cookie
2、Web Storage
1)Local Storage 与 Session Storage
2)存储量⼤,不⾃动发给服务端,js控制
3、indexDb
1)运⾏在浏览器上的⾮关系型数据库
4、PWA
1)基于缓存技术的应⽤模型
服务端渲染
SSR对于SPA性能优化比较明显
vue服务端渲染
const Vue = require('vue')
// 创建⼀个express应⽤
const server = require('express')()
// 提取出renderer实例
const renderer = require('vue-server-renderer').createRenderer()
server.get('*', (req, res) => {
// 编写Vue实例(虚拟DOM节点)
const app = new Vue({
data: {
url: req.url
},
// 编写模板HTML的内容
template: `<div>访问的 URL 是: {{ url }}</div>`
})
// renderToString 是把Vue实例转化为真实DOM的关键⽅法
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
// 把渲染出来的真实DOM字符串插⼊HTML模板中
res.end(`
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>${html}</body>
</html>
`)
})
})
server.listen(8080)
nuxt.js服务端渲染框架
1、基于Vue.js
2、服务端
3、路由
4、热加载
5、支持http2
react服务端渲染
import express from 'express' import React from 'react' import { renderToString } from 'react-dom/server' import App from './App' const app = express() // renderToString 是把虚拟DOM转化为真实DOM的关键⽅法 const RDom = renderToString(<App />) // 编写HTML模板,插⼊转化后的真实DOM内容 const Page = ` <html> <head> <title>test</title> </head> <body> <span>ssr </span> ${RDom} </body> </html> ` app.get('/index', function(req, res) { res.send(Page) }) // 配置端⼝号 const server = app.listen(8000)
文件打包
分析文件大小
npm install lodash echarts moment -D
const BundleAnalyzerPlugin = require('webpack-bundleanalyzer').BundleAnalyzerPlugin;
module.exports = {
configureWebpack: {
plugins: [
new BundleAnalyzerPlugin()
]
}
}
import moment from 'moment'
import _ from 'lodash'
Vue.config.productionTip = false
console.log(moment())
console.log(_.max([5,4,1,6,8]))
性能监控
// 控制台
performance.getEntriesByType('navigation')
performance
挖掘瓶颈——火焰图
重定向耗时:redirectEnd - redirectStart DNS查询耗时 :domainLookupEnd - domainLookupStart TCP链接耗时 :connectEnd - connectStart HTTP请求耗时 :responseEnd - responseStart 解析dom树耗时 : domComplete - domInteractive ⽩屏时间 :responseStart - navigationStart DOMready时间 :domContentLoadedEventEnd - navigationStart onload时间:loadEventEnd - navigationStart,也即是onload回调函数执⾏的时间。
LightHouse
chrome插件
命令行
npm install -g lighthouse lighthouse https://www.kaikeba.com/ --view
节流
// func是⽤户传⼊需要防抖的函数 // wait是等待时间 const throttle = (func, wait = 50) => { // 上⼀次执⾏该函数的时间 let lastTime = 0 return (...args)=>{ // 当前时间 let now = +new Date() // 将当前时间和上⼀次执⾏函数时间对⽐ // 如果差值⼤于设置的等待时间就执⾏函数 if (now - lastTime > wait) { lastTime = now func.apply(this, args) } } } setInterval( throttle(() => { console.log(1) }, 500), 1 )
防抖
// func是⽤户传⼊需要防抖的函数 // wait是等待时间 const debounce = (func, wait = 50) => { // 缓存⼀个定时器id let timer = 0 // 这⾥返回的函数是每次⽤户实际调⽤的防抖函数 // 如果已经设定过定时器了就清空上⼀次的定时器 // 开始⼀个新的定时器,延迟执⾏⽤户传⼊的⽅法 return function(...args) { if (timer) clearTimeout(timer) timer = setTimeout(() => { func.apply(this, args) }, wait) } }
dom
!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>DOM操作测试</title> </head>
<body> <div id="container"></div> </body> </html>
重绘与回流
lazy-load
// 获取所有的图⽚标签 const imgs = document.getElementsByTagName('img') // 获取可视区域的⾼度 const viewHeight = window.innerHeight || document.documentElement.clientHeight // num⽤于统计当前显示到了哪⼀张图⽚,避免每次都从第⼀张图⽚开始检查是否露出 let num = 0 function lazyload(){ for(let i=num; i<imgs.length; i++) { // ⽤可视区域⾼度减去元素顶部距离可视区域顶部的⾼度 let distance = viewHeight - imgs[i].getBoundingClientRect().top // 如果可视区域⾼度⼤于等于元素顶部距离可视区域顶部的⾼度,说明 元素露出 if(distance >= 0 ){ // 给元素写⼊真实的src,展示图⽚ imgs[i].src = imgs[i].getAttribute('data-src') // 前i张图⽚已经加载完毕,下次从第i+1张开始检查是否露出 num = i + 1 } } } // 监听Scroll事件 window.addEventListener('scroll', lazyload, false);
vue相关优化
v-if vs v-show
初始性能 VS 频繁切换性能
和渲染无关的数据,不要放在data上
data也不要嵌套过多层
nextTick
this.$nextTick(function() { // DOM 更新了 })
Object.freeze()
冻结数据,取消setters
路由懒加载
const router = new VueRouter({ routes: [ { path: '/foo', component: () => import('./Foo.vue') } ] })
keep-alive缓存页面
<template>
<div id="app">
<keep-alive>
<router-view/>
</keep-alive>
</div>
</template>
使用v-show复用dom
<template> <div class="cell"> <!--这种情况用v-show复用DOM,比v-if效果好--> <div v-show="value" class="on"> <Heavy :n="10000"/> </div> <section v-show="!value" class="off"> <Heavy :n="10000"/> </section> </div> </template>
<template>
<ul>
<li v-for="user in activeUsers" :key="user.id">{{ user.name }}</li>
</ul>
</template>
<script>
export default {
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
}
</script>
长列表性能优化
如果列表是纯粹的数据展示,不会有任何改变,就不需要做响应化
export default { data: () => ({ users: [] }), async created() { const users = await axios.get("/api/users"); this.users = Object.freeze(users); } };
如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容
<recycle-scroller class="items" :items="items" :item-size="24"> <template v-slot="{ item }"> <FetchItemView :item="item" @vote="voteItem(item)"/> </template> </recycle-scroller>
参考vue-virtual-scroller、vue-virtual-scroll-list
事件的销毁
Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。
created() { this.timer = setInterval(this.refresh, 2000) }, beforeDestroy() { clearInterval(this.timer) }
图片懒加载
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域 内的图片先不做加载, 等到滚动到可视区域后再去加载。
<img v-lazy="/static/img/1.png">
参考项目:vue-lazyload
第三方插件按需引入
像element-ui这样的第三方组件库可以按需引入避免体积太大。
import Vue from 'vue'; import { Button, Select } from 'element-ui'; Vue.use(Button) Vue.use(Select)
无状态的组件标记为函数式组件
<template functional>
<div class="cell">
<div v-if="props.value" class="on"></div>
<section v-else class="off"></section>
</div>
</template>
<script>
export default {
props: ['value']
}
</script>
子组件分割
<template>
<div>
<ChildComp/>
</div>
</template>
<script>
export default {
components: {
ChildComp: {
methods: {
heavy () { /* 耗时任务 */ }
},
render (h) {
return h('div', this.heavy())
}
}
}
}
</script>
变量本地化
<template>
<div :style="{ opacity: start / 300 }">
{{ result }}
</div>
</template>
<script>
import { heavy } from '@/utils'
export default {
props: ['start'],
computed: {
base () { return 42 },
result () {
const base = this.base // 不要频繁引用this.base
let result = this.start
for (let i = 0; i < 1000; i++) {
result += heavy(base)
}
return result
}
}
}
</script>
react
只传递需要的props
需要随便<Component{...props}>
key
无状态组件
pureComponent shouldComponentUpdate
渲染时机
少在render中绑定事件
redux + reselect
data扁平化
长列表 react + virtualized
只维护上下三屏的dom,模拟滚动
renderRow(item) { return ( <div key={item.id} className="row"> <div className="image"> <img src={item.image} alt="" /> </div> <div className="content"> <div>{item.name}</div> <div>{item.text}</div> </div> </div> ); }
import { List } from "react-virtualized"; <div className="list">
{this.list.map(this.renderRow.bind(this))}
</div>
// 改为
const listHeight = 600;
const rowHeight = 50;
const rowWidth = 800;
<div className="list">
<List
width={rowWidth}
height={listHeight}
rowHeight={rowHeight}
rowRenderer={this.renderRow.bind(this)}
rowCount={this.list.length} />
</div>
renderRow({ index, key, style }) {
return (
<div key={key} style={style} className="row">
<div className="image">
<img src={this.list[index].image} alt="" />
</div>
<div className="content">
<div>{this.list[index].name}</div>
<div>{this.list[index].text}</div>
</div>
</div>
);
}
Web Workers
浏览器渲染
雅虎军规
尽量减少 HTTP 请求个数——须权衡 使⽤ CDN(内容分发⽹络) 为⽂件头指定 Expires 或 Cache-Control ,使内容具有缓存性。 避免空的 src 和 href 使⽤ gzip 压缩内容 把 CSS 放到顶部 把 JS 放到底部 避免使⽤ CSS 表达式 将 CSS 和 JS 放到外部⽂件中 减少 DNS 查找次数 精简 CSS 和 JS 避免跳转 剔除重复的 JS 和 CSS 配置 ETags 使 AJAX 可缓存 尽早刷新输出缓冲 使⽤ GET 来完成 AJAX 请求 延迟加载 预加载 减少 DOM 元素个数 根据域名划分⻚⾯内容 尽量减少 iframe 的个数 避免 404 减少 Cookie 的⼤⼩ 使⽤⽆ cookie 的域 减少 DOM 访问 开发智能事件处理程序 ⽤ 代替 @import 避免使⽤滤镜 优化图像 优化 CSS Spirite 不要在 HTML 中缩放图像——须权衡 favicon.ico要⼩⽽且可缓存 保持单个内容⼩于25K 打包组件成复合⽂本

浙公网安备 33010602011771号