leslie1943/blog

性能优化: 前端性能优化 24 条建议(上)

Opened this issue · 0 comments

前端性能优化 24 条建议(上)

🍊 1: 减少HTTP请求

一个完整的 HTTP 请求需要经历 DNS 查找, TCP 握手, 浏览器发出HTTP请求, 服务器接收请求, 服务器处理请求并返回响应, 浏览器接收响应等过程

image

从这个例子可以看出, 真正下载数据的时间占比为 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 标签里的CSSJS文件都会堵塞渲染. 如果这些CSSJS需要加载和计息很久的话, 那么页面就会空白了. 所以文件要放在底部, 等HTML解析完了再加载JS文件

那么为什么CSS还要放在头部呢? 因为先加载HTML再加载CSS, 会让用户第一时间看到的页面没有样式的, 丑陋的页面,为了避免这种情况发生,就要将 CSS 文件放在头部了; JS也可以放在头部, 只要给 script加上defer属性就可以了, 异步下载, 延迟执行.

🍊 6: 使用字体图标iconfont代替图片图标

字体图标就是将图标制作成一个字体,使用时就跟字体一样,可以设置属性,例如font-sizecolor等,非常方便. 字体图标是矢量图,不会失真, 生成的文件特别小

  • 压缩字体文件: 使用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): 提取第三方库: 由于引入的第三方库一般都比较稳定, 不会经常改变. 所以将它们单独提取出来, 作为长期缓存是一个更好的选择. 这里需要使用 webpack4splitChunk 插件 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- 将渲染树每个节点绘制到屏幕

image

  • 重排: 当改变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)
  }
})