加载时性能优化
一、预加载
预加载包括:DNS Prefetch、Preconnect、Prefetch、Prerender、Preload
。
1. DNS Prefetch 预解析DNS
1) DNS解析过程如下:
- 先检查本地hosts文件中是否有映射,有则使用本地映射。
- 查找本地DNS缓存,有则走本地DNS缓存。
- 根据配置在TCP/IP参数中设置DNS查询服务器,并向其进行查询,这是本地DNS查询。
- 如果本地服务器查不到,则使用递归和迭代两种方式逐级向上查找,直到根服务器。
2)前端设置dns预拉取
DNS解析可能耗时很长,前端可以通过一些设置,预拉取代解析的域名,帮助解析加速。
<link rel="dns-prefetch" href="//baidu.com">
复制代码
DNS Prefetch
是浏览器提供的API,它会告诉浏览器,我一会儿可能会在这个域名下下载资源,请你帮我提前解析下这个域名。
2. Preconnect 与连接
建立连接需要TCP三次握手,如果有TLS/SSL协议,加密会更加耗时。使用Preconnect可以预先建立连接。
<link rel="preconnect" href="//baidu.com">
<link rel="preconnect" href="//baidu.com" crossorigin>
复制代码
3. Prefetch 资源预拉取
Prefetch仅仅是拉取资源,不会进行预处理。as
属性用来标识资源类型。
<link rel="prefetch" href="/prefetch.js" as="script">
复制代码
4. Prerender 预渲染
Prerender会预拉取资源,也会进行预执行。
<link rel="prerender" href="//sample.com/nextpage.html">
复制代码
预渲染一个html文件,这个html文件中依赖的script脚本和样式也会被下载下来。
5. Preload 预加载
<link rel="preload" href="./nextpage.js" as="script">
复制代码
当遇到需要Preload的资源时,浏览器会立即进行预获取,并将结果放在内存中。待使用该资源时再执行。
资源的获取不会影响页面parse与load事件触发。
Prefetch和Preload的区别?
这两个都是用来获取资源的,但是优先级不一样,preload
优先级高,要求浏览器立即进行预获取.
prefetch
优先级较低,浏览器会判断在自己空闲的时候进行预拉取。prefetch
适用于未来可能会用到的资源的拉取。
webpack中使用资源预加载
只需要添加注释,webpack就会知道你需要对这个chunk进行预加载。
// prefetch
import(/* webpackPrefetch: true */ './sub1.js');
// preload
import(/* webpackPreload: true */ './sub2.js')
复制代码
6. 基于JS的预加载
对于不兼容以上加载方式的浏览器,可以使用js来进行预加载。
let img = new Image();
img.src = '/static/img/prefetch.jpg';
img.id = 'imgId';
img.onload = function() {
document.body.removeChild(imgId);
}
复制代码
二、缓存
1. 本地数据存储
localStorage
,sessionStorage
,indexedDB
2. 内存缓存
内存缓存是浏览器帮我们实现的优化,如果一个资源被多次使用,浏览器会把它存在内存中。
3. Cache API + service Worker
如果没有命中内存缓存,还会看看Cache API里的缓存。
// index.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js').then(function () {
// 注册成功
});
}
复制代码
// sw.js
self.addEventListener('fetch', function (e) {
// 如果有cache则直接返回,否则通过fetch请求
e.respondWith(
caches.match(e.request).then(function (cache) {
return cache || fetch(e.request);
}).catch(function (err) {
console.log(err);
return fetch(e.request);
})
);
});
复制代码
以上代码会拦截网络请求,检查是否有缓存的请求内容,如果有则返回缓存,否则继续发送请求。
与内存缓存不同,Cache API提供的缓存是永久性的,关闭浏览器,下次访问还可以使用。
4. HTTP缓存
1) 强缓存
Expires
Cache-Control: max-age=300
2) 协商缓存
5. Push Cache
Push Cache 是HTTP2提供的push功能带来的。客户端请求一个资源,服务端将未来将会用到的资源也推给客户端。比如请求www.baidu.com,服务端返回了html,也把css样式表推给客户端。
三、网络层
1. 减少重定向请求
每次重定向都是有请求耗时的,建议避免过多的重定向。站点永久迁移就用301,不要用临时重定向。
2. CDN内容分发网络
对于使用CDN的资源,DNS解析会将CDN资源的域名解析到CDN服务的负载均衡器上。降低时延。
3. 减少http请求数量
四、页面解析
1. 资源在文档中的位置
css放在上面,js放在下面。js会阻塞渲染。
2. 使用defer和async
防止js脚本执行阻塞dom构建。
3. 文档压缩
五、静态资源
1. 图片预加载、懒加载
2. 压缩
3. 缓存
运行时性能优化
一、避免强制同步布局
var $ele = document.getElementById('main');
$ele.classList.remove('large');
var height = $ele.offsetHeight;
复制代码
删除了一个class,再去获取元素高度,浏览器并不知道删除的class是否影响元素高度,所以浏览器会渲染一次。即使这个class并没有改变高度,浏览器也会渲染。
正确的做法:先获取,后删除class
var height = $ele.offsetHeight;
var $ele = document.getElementById('main');
$ele.classList.remove('large');
复制代码
二、长列表优化
其大致的实现思路如下:
- 监听页面滚动(或者其他导致视口变化的事件)
- 滚动时根据滚动的距离计算需要展示的列表项
- 将列表项中展示的数据与组件替换成当前需要展示的内容
- 修改偏移量到对应的位置
vue-virtual-scroll-list
react-virtual-scroll-list
三、避免js执行时间过长
js引擎线程和渲染线程是互斥的,js执行时间过长,会导致无法及时渲染,出现掉帧的情况。
requestIdleCallback
四、并行计算-service worker
// index.js
const worker = new Worker('worker.js');
worker.addEventListener('message', function (e) {
console.log(`result is ${e.data}`);
}, false);
worker.postMessage('start');
复制代码
// worker.js
self.addEventListener('message', function (e) {
if (e.data === 'start') {
// 一些密集的计算……
self.postMessage(result);
}
}, false);
复制代码
五、Composite分层绘制
浏览器是会分层绘制的。单个图层发生变化,只渲染这一个图层。absolute,float,transform3D都会生成一个新图层。
六、滚动事件性能优化
防抖、节流
七、Passive event listeners
document.addEventListener('touchstart', function (e) {
// 做了一些操作……
e.preventDefault();
}, true);
复制代码
touchstart
中调用了e.preventDefault()
会阻止页面滚动即缩放。那么浏览器怎么知道要阻止页面滚动缩放呢?因为调用了e.preventDefault()
,就是说浏览器必须执行完监听回调里的代码才知道开发者有没有禁止默认事件。
有时候我们不会阻止默认事件,有没有办法告诉浏览器,让他不用等了,直接执行回调就可以了呢?
document.addEventListener('touchstart', function (e) {
// 做了一些操作……
}, {passive: true});
复制代码