性能优化
分析工具
lighthouse
performance
webpack-bundle-analyzer
webpack中分析打包文件的插件
性能优化的目的
1、对用户而言:页面加载得更快,响应更及时,体验更友好,减少用户流失
2、对公司而言:减少资源请求数,减少带宽,节省成本
优化途径有很多,大致可从以下两个角度出发:
a、页面级优化
b、代码级优化
页面级优化
可以用两个角度出发
1、提高请求速度
2、减少请求次数和体积
1、DNS预解析
当浏览器从(第三方)服务器请求资源时,必须先将该跨域域名解析为 IP 地址,然后浏览器才能发出请求。 此过程称为 DNS 解析。 DNS 缓存可以帮助减少此延迟,而 DNS 解析可以导致请求增加明显的延迟,此延迟可能会大大降低加载性能。
会经过:浏览器dns缓存 > 本地host > dns服务器
添加以下link,告诉浏览器先做一个dns的预查询,用到的时候可以直接拿到对于ip
dns-prefetch
可帮助开发人员掩盖 DNS 解析延迟。 HTML 元素 通过 dns-prefetch
的 rel 属性值提供此功能。然后在 href 属性中指要跨域的域名:
使用
<html>
<head>
<link rel="dns-prefetch" href="https://cdn.forguo.cn">
<!-- and all other head elements -->
</head>
<body>
<!-- your page content -->
</body>
</html>
<html>
<head>
<link rel="dns-prefetch" href="https://cdn.forguo.cn">
<!-- and all other head elements -->
</head>
<body>
<!-- your page content -->
</body>
</html>
2、预加载
告诉浏览器在合理的时机去提前加载资源
a、preload
使用link
的preload
属性预加载一个资源。
<link rel="preload" href="style.css" as="style">
<link rel="preload" href="style.css" as="style">
as
属性可以指定预加载的类型,除了style
还支持很多类型,常用的一般是style
和script
,css
和js
。
b、prefetch
prefetch
和preload
差不多,prefetch
是一个低优先级的获取,通常用在这个资源可能会在用户接下来访问的页面中出现的时候。
当然对当前页面的要用preload,不要用prefetch,可以用到的一个场景是在用户鼠标移入a标签时进行一个prefetch。
c、preconnect
preconnect
和dns-prefetch
做的事情类似,提前进行TCP,SSL握手,省去这一部分时间,基于HTTP1.1(keep-alive)和HTTP2(多路复用)的特性,都会在同一个TCP链接内完成接下来的传输任务。
d、script加标记
当浏览器解析至script标签时,浏览器的主线程就会等待script,或者运行script,然后继续开始构建
我们可以给script标签增加标记,使其异步(延迟)运行。
async标记
<script src="main.js" async>
<script src="main.js" async>
async
标记告诉浏览器在等待js下载期间可以去干其他事,当js下载完成后会立即(尽快)执行,多条js可以并行下载。
async的好处是让多条js不会互相等待,下载期间浏览器会去干其他事(继续解析HTML等),异步下载,异步执行。
defer标记
<script src="main.js" defer></script>
<script src="main.js" defer></script>
与async一样,defer
标记告诉浏览器在等待js下载期间可以去干其他事,多条js可以并行下载, 不过当js下载完成之后不会立即执行,而是会等待解析完整个HTML之后在开始执行,而且多条defer标记的js会按照顺序执行,
<script src="main.js" defer></script>
<script src="main2.js" defer></script>'
<script src="main.js" defer></script>
<script src="main2.js" defer></script>'
即使main2.js先于main.js下载完成也会等待main.js执行完后再执行。
3、使用HTTP2
HTTP/1.1与HTTP/2的区别
1、新的二进制格式(Binary Format),HTTP1.x解析是基于文本的,基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
2、多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。
3、header压缩,如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
4、服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能。
HTTP/2的多路复用和HTTP/1.X中的长连接复用的区别
1、HTTP/1.X 一次请求-响应,建立一个连接,用完关闭;每一个请求都要建立一个连接;
2、HTTP/1.1 Pipeling解决方式为,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的线头阻塞;
3、HTTP/2多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行;
nginx开启http2
server {
listen 443 ssl http2; # 加一句 http2.
server_name domain.com;
}
server {
listen 443 ssl http2; # 加一句 http2.
server_name domain.com;
}
4、使用缓存
强缓存
js和css添加强缓存
内容不变时,直接从缓存中读取 内容变化时,从服务器获取新的资源
状态码均为200
协商缓存
html添加协商缓存
向服务器发送请求,服务器判断内容是否变化,返回304,浏览器从缓存中读取 内容变化,返回200,并返回新的资源
5、使用CDN
CDN会把源站的资源缓存到CDN服务器,当用户访问的时候就会从最近的CDN服务器拿取资源而不是从源站拿取,这样做的好处是分散了压力,同时也会提升返回访问速度和稳定性。
6、资源压缩与合并
对资源进行合理的压缩以及合并,减少请求次数和大小
1、html、css、js、image等压缩
2、雪碧图
3、字体图标(iconfont)
4、合并js、css等资源
5、开启GZIP,压缩资源
代码级优化
1、合理使用闭包
2、事件委托
3、减少DOM操作
多次dom操作可使用createFragment
,创建虚拟dom切片
4、长列表优化
将不在视图范围内的dom使用占位元素代替
5、懒加载
路由懒加载、组件懒加载、图片懒加载
import()代替import
6、减少重绘和回流
- 重绘
改变元素的color,background等外观样式,导致重绘
- 回流
改变元素的大小,位置等样式,导致回流
- 优化
合并样式修改
批量操作DOM
回流一定重绘,重绘不一定回流
7、节流和防抖
节流 throttle
稀释函数的执行频率
function throttle(fn, delay) {
let lastTime = 0
return function () {
let now = Date.now()
if (now - lastTime > delay) {
fn.apply(this, arguments)
lastTime = now
}
}
}
function throttle(fn, delay) {
let lastTime = 0
return function () {
let now = Date.now()
if (now - lastTime > delay) {
fn.apply(this, arguments)
lastTime = now
}
}
}
防抖 debounce
固定时间不可再次触发
function debounce(fn, delay) {
let timeout = null
return function () {
timeout && clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
function debounce(fn, delay) {
let timeout = null
return function () {
timeout && clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}