共计 6029 个字符,预计需要花费 16 分钟才能阅读完成。
前言
事实上, 只有
10%-20%
的最终用户响应时间是发在从 Web 服务器获取 HTML 文档并传送到浏览器中的。如果希望能够有效地减少页面的响应时间,就必须关注剩余80%-90%
的最终用户体验。
–Steve Souders在这篇博客中,我根据工作中的实际项目经验和一些测试的经验中总结出了前端页面在性能上优化方案。其中一些经验吸收自《高性能网站建设指南》Steve Souders 著 电子工业出版社。
一、代码相关优化
1. 将样式表放在首部 - 使用 link 标签将样式表放在文档的 HEAD 中
-
遵循 HTML 规范
,将样式表放在头部,可以有效避免白屏
和无样式内容的闪烁
。
<!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></title>
<!-- 使用 link 标签将样式表放在文档的 HEAD 中 -->
<link rel="stylesheet" href="example.css">
</head>
<body></body>
</html>
2. 将脚本放在底部
- 将脚本放在顶部会造成的影响:
脚本阻塞对其后面内容的显示
;脚本会阻塞对其后面组件的下载
; - 将脚本放在
底部 </body>
标签之前, 类似于 document.body.appendChild(yourScript), 不会阻塞页面内容的呈现,而且页面中的可视组件可以尽早下载。
<!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></title>
<link rel="stylesheet" href="example.css">
</head>
<body>
<!-- 将脚本放在底部 -->
<script src="example.js"></script>
</body>
</html>
3. 减少 HTTP 请求
1) CSS Sprites (雪碧图)
将
多个图片
合成一张图片
,通过 background-position 来定位所需要的图片。每次请求的话只需要请求一张图片
减少 http 请求。(如果使用图标的话建议使用 svg, 也可以使用 iconfont)
- 合成雪碧图的工具有很多
- 本地工具:https://github.com/iwangx/sprite(国人写的)
- 在线工具 https://www.toptal.com/develo…
本地工具:
在线工具:
2) 内联图片和脚本
- 通过内联图片和脚本无需额外的 HTTP 请求,图片小于 10K 的可以设置内联为 base64 位。
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4SUJRXhpZgAATU>
3) 合并脚本和样式表
- 一般来说,使用外部脚本和样式表对性能更有利,然而如果将模块化的代码分开放到
多个小文件
中,会降低性能
,每个文件都会导致一个额外的
HTTP 请求
4. 使用外部 Javascript 和 css
Good
<link rel="stylesheet" href="example.css">
<script src="example.js"></script>
bad
<style>
// code
</style>
<script>
// code
</script>
- 使用外部 Javascript 和 Css 的主要作用有:
可以配置缓存
有利于组件重用
5. 使用 CDN (内容分发网络 Content Delivery Network)
CDN 是构建在网络之上的内容分发网络,依靠部署在
各地的边缘服务器
,通过中心平台的负载均衡
、内容分发
、调度
等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN 的关键技术主要有内容存储
和分发技术
。– 摘自百度百科
- 通过 CDN 引入的资源目前基本都是使用目前最新的
HTTP2 协议
,所以在性能上可以做到极致优化,感谢 CDN。 - BootCDN – Bootstrap 中文网开源项目免费 CDN 加速服务
- UNPKG
6. 代码压缩
1) Gzip 压缩
gzip 压缩可以节省
50%-70%
的网络开销
浏览器支持的压缩类型可以通过 network 的 Accept-Encoding:
gzip
,deflate
来查看。支持 deflate 的浏览器也支持 gzip,但很多浏览器支持 gzip 却不支持 deflate
,因此 gzip 是最理想的压缩方法
- node 端 使用 compression 如果是
webpack
项目可以看下面的Vue 首屏加载时间优化
方案里的 gzip 压缩
// npm install compression --save-dev
const compression = require('compression')
2) 代码压缩
前端打包压缩的有 grunt,gulp,webpack,可以对 HTML,CSS,Javascript 代码压缩
二、服务器相关优化
本文中使用
nginx
服务器进行相关配置,使用apache
同样可以做到相关优化,具体操作请另 Google
1. 开启 gzip
压缩
- 服务端配置 gzip 压缩
gzip on; # 开启 Gzip
gzip_static on; # 开启静态文件压缩
gzip_min_length 1k; # 不压缩临界值,大于 1K 的才压缩
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/xml+rss; # 进行压缩的文件类型
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_disable "MSIE [1-6]\.";
- 我的服务器 nginx 相关的配置:
2. 开启 HTTP2
HTTP2 在前端性能上主要表现在:请求和响应的多路复用、头部压缩
- 感受下多路复用
nginx 服务器配置 HTTP2
- 使用 http2 需要配合 https 使用
- 使用 https 需要 ca 证书 阿里云 https 证书购买 (有免费的 ca 证书)
3. 开启缓存
添加 Expires 头(强缓存)
个人站点相关配置
nginx 配置
location ~.*\.(svg|woff|js|css){
root /yourFilePath;
expires 1d;
}
- Web 服务器使用
Expires
头来告诉 Web 客户端它可以使用一个组件的当前副本,直到指定的时间为止 HTTP 规范中简要地称该头为“在这一日期时间之后,响应将被认为是无效的”。它在 HTTP 响应中发送
expires: Thu, 30 May 2019 20:51:42 GMT
- 上面的 Expires 头告诉浏览器该响应的有效性持续到 2019 年 5 月 30 日为止。如果为页面中的一个图片返回了这个头,浏览器在后续的页面浏览中会使用缓存的图片,将 HTTP 请求的数量减少一个
Max-Age 和 mod_expires
- 个人站点的 css 文件使用
强缓存 cache-control: max-age
- nginx 配置
server {add_header Cache-Control max-age=72000;}
- 在解释缓存如何很好地改善传输性能之前,需要提及除了 Expires 头之外的另一种选择。
HTTP 1.1 引入了 Cache-Control 头来克服 Expires 头的限制
- 因为 Expires 头使用一个特定的时间,它
要求服务器和客户端的时钟严格同步
。另外,过期日期需要经常检查,并且一旦未来这天到来了,还需要在服务器配置中提供个新的日期。 - Cache-Control 使用 max-age 指令指定组件被缓存多久, 如果从组件被请求开始过去的秒数少于 max-age, 浏览器就使用缓存的版本,这就避免了额外的 HTTP 请求。
一个长久的 max-age 头可以将刷新窗设置为未来 10 年
。
Cache-Control: max-age=315360000
- Expires 和 Cache-Control max-age. 如果两者同时出现,HTTP 规范规定 max-age 指令将
重写 Expires 头
。 - 建议使用
Cache-Control max-age
,如果使用 expires 你需要担心它带来的时钟同步
和配置维护
问题。
配置 ETag(协商缓存)
Vue 官方文档的 Expires 相关配置
浏览器必须产生这个 HTTP 请求,执行有效性检查, 但这仍
比简单地下载所有已过期的组件效率要高
(对比强缓存
)。如果浏览器缓存中的组件是有效的(即它能够和原始服务器上的组件相匹配),原始服务器不会返回整个内容,而是返回一个304 Not Modifed
状态码。
附:Vue 首屏加载时间过长详细优化方案
- 首先附一张优化过后的图
-
首屏加载时间
从原来的10s
到2s
,测试的个人站点
注:我在优化 vue 项目的时候使用的是
vue@2.6.6
,vue-cli@3.4
。如果是 cli2 的项目影响也不大,优化的方案是结合服务端和 webpack 的。
vue-cli 脚手架默认配置已经大幅度优化了前端整体的性能,在此基础上,我又使用了三个优化项增加了大幅度提升
1. gzip 压缩
- 结合
服务器相关优化第一条:开启 gzip 压缩
- 下面是前端项目中
vue.config.js
中的配置
// 需要 npm install compression-webpack-plugin --save-dev
const CompressionWebpackPlugin = require('compression-webpack-plugin')
// 定义当前环境
const ENV = process.env.NODE_ENV || 'development'
module.exports = {
configureWebpack: config => {
// 如果是开发环境的话,开启压缩
if (ENV === 'production') {
// 参数配置文档: https://www.webpackjs.com/plugins/compression-webpack-plugin/
config.plugins.push(new CompressionWebpackPlugin({
algorithm: 'gzip',
test: /\.(js|css|html)$/,
threshold: 10240,
minRatio: 0.8
}))
}
}
}
2. 使用 CDN 内容分发网络
在
index.html
文件中通过环境来判断是否引入 cdn 文件,在vue.config.js
文件中 webpack 通过环境判断是否使用 cdn 引入文件的全局变量
- 使用 CDN 需要在
webpack
和index.html
进行相关配置
第一步 配置 vue.config.js
,只需要在刚才 配置 Gzip 压缩的基础上再加一段代码
:
const CompressionWebpackPlugin = require('compression-webpack-plugin')
const ENV = process.env.NODE_ENV || 'development'
module.exports = {
configureWebpack: config => {if (ENV === 'production') {
config.plugins.push(new CompressionWebpackPlugin({
algorithm: 'gzip',
test: /\.(js|css|html)$/,
threshold: 10240,
minRatio: 0.8
}))
// 配置 externals 就是当使用 CDN 进入的 js 文件在当前项目中可以引用
// 比如在开发环境引入的 vue 是 import Vue from 'vue', 这个大写的 Vue 就是对应的下面的大写的 Vue
config.externals = {
'vue': 'Vue',
'vue-router': 'VueRouter',
'axios': 'axios'
}
}
}
}
第二步 配置index.html
, 在 body 里使用 EJS 语法判断是否为生产环境
<body>
<div id="app"></div>
<% if (NODE_ENV === 'production') { %>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.2/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<% } %>
</body>
3. 配置 sourceMap
- devtool | webpack 中文网 你可以根据官网来对开发环境和生产环境进行详细配置
- 当然也可以像我一样直接
productionSourceMap: false
干掉生产环境的 sourceMap
const CompressionWebpackPlugin = require('compression-webpack-plugin')
const ENV = process.env.NODE_ENV || 'development'
module.exports = {
configureWebpack: config => {if (ENV === 'production') {
config.plugins.push(new CompressionWebpackPlugin({
algorithm: 'gzip',
test: /\.(js|css|html)$/,
threshold: 10240,
minRatio: 0.8
}))
config.externals = {
'vue': 'Vue',
'vue-router': 'VueRouter',
'axios': 'axios'
}
}
},
// 禁用生产环境的 sourceMap
productionSourceMap: false
}
4. 使用 HTTP2
请参考服务端优化第二条
结语
前端性能优化至关重要,文中提及的是我认为比较重要的几点,以后遇到更好的方案会补充进来。当然你也可以在评论区留言我们一起探讨,有错误的地方欢迎指出。