性能优化: 前端性能优化 24 条建议(上)
Opened this issue · 0 comments
前端性能优化 24 条建议(上)
🍊 1: 减少HTTP请求
一个完整的 HTTP 请求需要经历 DNS 查找, TCP
握手, 浏览器发出HTTP请求, 服务器接收请求, 服务器处理请求并返回响应, 浏览器接收响应等过程
从这个例子可以看出, 真正下载数据的时间占比为 13.05 / 204.16 = 6.39%
, 文件越小, 这个比例越小, 文件越大, 比例就越高.这就是为什么要建议将多个小文件合并为一个大文件, 从而减少 HTTP 请求次数的原因.
🍊 2: 使用HTTP2
与HTTP1.1
相比, 其优点如下:
解析速度快
: 服务器解析HTTP1.1
的请求时, 必须不断地读入字节, 直到遇到分隔符 CRLF 为止. 而解析 HTTP2 的请求就不用这么麻烦, 因为HTTP2
是基于帧的协议, 每个帧都有表示帧长度的字段多路复用
:HTTP1.1
如果要同时发起多个请求, 就得建立多个TCP
连接, 因为一个TCP
连接同时只能处理一个HTTP1.1
的请求. 在HTTP2
上, 多个请求可以共用一个TCP
连接, 这称为多路复用, 同一个请求和响应用一个流来表示, 并有唯一的流ID
来标识. 多个请求和响应在TCP
连接中可以乱序发送, 到达目的地后再通过流ID
重新组建首部压缩
: 可以把相同的首部存储起来, 仅发送它们之间不同的部分, 就可以节省不少的流量, 加快请求的时间优先级
:HTTP2
可以对比较紧急的请求设置一个较高的优先级, 服务器在接收到这样的请求后, 可以优先处理.流量控制
: 由于一个 TCP 连接流量带宽(根据客户端到服务器的网络带宽而定)是固定的, 当有多个请求并发时, 一个请求占的流量多, 另一个请求占的流量就会少.流量控制可以对不同的流的流量进行精确控制.服务器推送
: 服务器可以对一个客户端请求发送多个响应,换句话说, 除了对最初请求的响应外, 服务器还可以额外向客户端推送资源, 而无需客户端明确地请求
🍊 3: 使用服务端渲染
客户端渲染
: 获取 HTML 文件, 根据需要下载 JavaScript 文件, 运行文件, 生成 DOM, 再渲染.服务端渲染
:服务端返回 HTML 文件, 客户端只需解析 HTML.- 优点: 首屏渲染快, SEO好.
- 缺点: 配置麻烦, 增加了服务器的计算压力
🍊 4: 静态资源使用CDN
内容分发网络(CDN)是一组分布在多个不同地理位置的 Web
服务器, 当服务器离用户越远时, 延迟越高. CDN 就是为了解决这一问题, 在多个位置部署服务器, 让用户离服务器更近, 从而缩短请求时间.CDN原理
🍊 5: 将CSS放在文件头部, JavaScript文件放在底部
所有放在 head
标签里的CSS
和JS
文件都会堵塞渲染. 如果这些CSS
和JS
需要加载和计息很久的话, 那么页面就会空白了. 所以文件要放在底部, 等HTML解析完了再加载JS
文件
那么为什么CSS
还要放在头部呢? 因为先加载HTML
再加载CSS
, 会让用户第一时间看到的页面没有样式的, 丑陋的页面,为了避免这种情况发生,就要将 CSS
文件放在头部了; JS也可以放在头部, 只要给 script
加上defer
属性就可以了, 异步下载, 延迟执行.
🍊 6: 使用字体图标iconfont代替图片图标
字体图标就是将图标制作成一个字体,使用时就跟字体一样,可以设置属性,例如font-size
、color
等,非常方便. 字体图标是矢量图,不会失真, 生成的文件特别小
- 压缩字体文件: 使用
fontmin-webpack
插件对字体文件进行压缩.
🍊 7: 善用缓存,不重复加载相同的资源
为了避免用户每次访问网站都得请求文件, 我们可以通过添加 Expires 或 max-age 来控制这一行为;
Expires
设置了一个时间, 只要在这个时间之前, 浏览器都不会请求文件, 而是直接使用缓存.- 而 max-age 是一个相对时间, 建议使用 max-age 代替 Expires
不过这样会产生一个问题, 当文件更新了怎么办? 怎么通知浏览器重新请求文件 ==> 可以通过更新页面中引用的资源链接地址, 让浏览器主动放弃缓存, 加载新资源. 具体做法是把资源地址 URL 的修改与文件内容关联起来, 也就是说, 只有文件内容变化, 才会导致相应 URL 的变更, 从而实现文件级别的精确缓存控制.
🍊 8: 压缩文件
压缩文件可以减少文件下载时间,让用户体验性更好
JavaScript
==>UglifyPlugin
CSS
==>MiniCssExtractPlugin
HTML
==>HTMLWebpackPlugin
还可以使用gzip
压缩, 通过向HTTP
请求头中的Accept-Encoding
头来添加gzip
标识来开启这一功能.当然服务器也要支持这一功能.
//gzip 是目前最流行和最有效的压缩方法. 举个例子, 我用 Vue 开发的项目构建后生成的 app.js 文件大小为 1.4MB, 使用 gzip 压缩后只有 573KB, 体积减少了将近 60%.
// 下载插件
// npm install compression-webpack-plugin --save-dev
// npm install compression
// webpack配置
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
plugins: [new CompressionPlugin()],
}
// node 配置
const compression = require('compression')
// 在其他中间件前使用
app.use(compression())
🍊 9: 图片优化
- (1) 图片延迟加载: 在页面中, 先不给图片设置路径, 只有当图片出现在浏览器的可视区域时才去加载真正的图片, 这就是延迟加载.
// 将图片设置成这样, 在也能不可见时图片不会加载
// <img data-src="https://avatars0.githubusercontent.com/u/22117876?s=460&u=7bd8f32788df6988833da6bd155c3cfbebc68006&v=4">
// 等页面可见时
const img = document.querySelector('img')
img.src = img.dataset.src
- (2) 响应式图片: 响应式图片的优点是浏览器能够根据屏幕大小自动加载合适的图片
<!-- 通过 picture 实现 -->
<picture>
<source srcset="banner_w1000.jpg" media="(min-width: 801px)">
<source srcset="banner_w800.jpg" media="(max-width: 801px)">
<img src="banner_w800.jpg" alt="">
</picture>
@media (min-width: 769px) {
.bg {
background-image: url(bg1080.jpg);
}
}
@media (max-width: 768px) {
.bg {
background-image: url(bg768.jpg);
}
}
- (3) 调整图片大小:
缩略图
和真实图片
- (4) 降低图片质量: 压缩图片,
image-webpack-loader
// webpack配置
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use:[
{
loader: 'url-loader',
options: {
limit: 10000, /* 图片大小小于1000字节限制时会自动转成 base64 码引用*/
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
/*对图片进行压缩*/
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
]
}
- (5) 尽可能利用
CSS3
效果代替图片 - (6) 使用
webp
格式的图片: WebP 的优势体现在它具有更优的图像数据压缩算法, 能带来更小的图片体积, 而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性, 在 JPEG 和 PNG 上的转化效果都相当优秀、稳定和统一
🍊 10: 通过 webpack 按需加载代码, 提取第三方库代码,减少 ES6转为ES5的冗余代码
懒加载或者按需加载, 是一种很好的优化网页或应用的方式. 这种方式实际上是先把你的代码在一些逻辑断点处分离开, 然后在一些代码块中完成某些操作后, 立即引用或即将引用另外一些新的代码块. 这样加快了应用的初始加载速度, 减轻了它的总体体积, 因为某些代码块可能永远不会被加载.
- (1): 根据文件内容生成文件名: 通过配置 output 的 filename 属性可以实现这个需求. filename 属性的值选项中有一个 [contenthash], 它将根据文件内容创建出唯一 hash. 当文件内容发生变化时, [contenthash] 也会发生变化.
- (2): 提取第三方库: 由于引入的第三方库一般都比较稳定, 不会经常改变. 所以将它们单独提取出来, 作为长期缓存是一个更好的选择. 这里需要使用
webpack4
的splitChunk
插件cacheGroups
选项
optimization: {
runtimeChunk: {
name: 'manifest' // 将 webpack 的 runtime 代码拆分为一个单独的 chunk.
},
splitChunks: {
cacheGroups: {
vendor: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
},
}
},
- (3) 减少 ES6 转为 ES5 的冗余代码
# npm i -D @babel/plugin-transform-runtime @babel/runtime
# 在 .babelrc文件中
"plugins": [
"@babel/plugin-transform-runtime"
]
🍊 11: 减少重绘重排
浏览器渲染过程:
# 1- 解析 HTML 生成 DOM 树
# 2- 解析 CSS 生成 CSSDOM 规则树
# 3- 将 DOM 树 和 CSSDOM 规则树 合并在一起生成渲染树
# 4- 遍历渲染树布局, 计算每个节点的位置大小信息
# 5- 将渲染树每个节点绘制到屏幕
重排
: 当改变DOM元素的位置或者大小时, 会导致浏览器重新生成DOM树,这个过程叫重排
重绘
: 当重新生成渲染树后,就要将渲染树每个节点绘制到屏幕,这个过程叫重绘
- 不是所有的动作都会导致
重排
,例如改变字体颜色,只会导致重绘
.记住,重排会导致重绘,重绘不会导致重排
# >>>>>> 重排动作
# 添加或删除可见的 DOM 元素
# 元素位置改变
# 元素尺寸改变
# 内容改变
# 浏览器窗口尺寸改变
- 如何减少
重排重绘
?
# 用 JavaScript 修改样式时, 最好不要直接写样式, 而是替换 class 来改变样式.
# 如果要对 DOM 元素执行一系列操作, 可以将 DOM 元素脱离文档流, 修改完成后, 再将它带回文档.推荐使用隐藏元素(display:none)或文档碎片(DocumentFragement), 都能很好的实现这个方案.
🍊 12: 使用事件委托
事件委托利用了事件冒泡, 只指定一个事件处理程序,就可以管理某一类型的所有事件. 所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术, 使用事件委托可以节省内存.
<ul>
<li>苹果</li>
<li>香蕉</li>
<li>凤梨</li>
</ul>
// good
document.querySelector('ul').onclick = (event) => {
const target = event.target
if (target.nodeName === 'LI') {
console.log(target.innerHTML)
}
}
// bad
document.querySelectorAll('li').forEach((e) => {
e.onclick = function() {
console.log(this.innerHTML)
}
})