概述
找到目标逐个击破
明确优化方向
从一个面试题引出前端优化需要注意的几个方向:
从用户输入URL到页面加载完成,这中间都发生了what
我希望各位读者可以在心中思考一下这一问题,在这一过程中的每个阶段都可在性能优化中做文章。 现在我们简单的复习一下此过程: 首先,需要通过DNS(域名解析系统)将URL解析为对应的IP地址,然后与此IP地址相关的服务器建立TCP网络连接,之后,客户端向服务端发起HTTP请求(request),服务端接收请求并处理后,将结果数据放在HTTP响应中(response)返回给客户端,获取服务端的响应数据后,浏览器对数据进行渲染,渲染完毕,页面展示在浏览器中,并时刻响应用户的进一步操作。
将这一流程切分为如下阶段进行逐一分析:
- DNS解析
- TCP连接
- HTTP请求request
- 服务端处理请求,HTTP响应返回数据
- 浏览器获得响应数据并解析,将结果展示给用户
大家可以看到,每次URL改变时,都需执行以上的五个阶段,因此在考虑性能优化方案时,我们都需要从以上五个阶段来考虑。
实践检验理论
接下来的任务,就是针对这五个阶段,进行细化、分解,逐个提出问题,逐个击破
具体工作分解
- DNS解析花费时间,可以减少解析次数或将解析前置吗?能
- 浏览器DNS缓存
- DNS prefetch
- TCP的三次握手有无解决方案?有
- 长链接
- 预链接
- 接入SPDY协议
- 前端的HTTP请求
- 减少请求次数
- 减少请求体积
- 在部署时将静态资源放在离我们较近的CDN上
以上提到的四点都是基于网络层面的优化,下面是基于浏览器方面的优化
- 资源加载优化
- 服务端渲染
- 浏览器缓存机制
- DOM树的构建
- 页面排版和渲染过程
- 回流与重绘的权衡
- 合理的DOM操作
网络层面的优化
在上一章中分析的五个阶段,主要涉及到网络层面的优化,主要有以下三个阶段
- DNS解析
- TCP连接
- HTTP请求/响应
前两个阶段主要与服务端相关,因此,我们主要关注HTTP连接这一方面的优化,主要分为两大方向:
- 减少请求次数
- 减少单次的请求体积
以上两点主要对应开发中常见的操作: 资源压缩与合并。现今主要是通过构建工具完成此方面的工作。因此,主要讲一下webpack的使用
webpack的构建瓶颈
对于每个用过webpack的读者都使用过‘打包’和‘压缩’,此处主要将注意力放在webpack的性能优化上
webpack的优化瓶颈主要分为以下两个方面:
- 构建过程耗时长
- 打包结果体积大
webpack优化方案
构建结果分析
项目完成之后,建议开发人员先打一个版,看一下效果,然后根据结果去做优化,做到有的放矢
文件结构可视化-webpack-bundle-analyzer
在打包之前先安装此可视化工具,它会以矩形树图的形式将包内各个模块的大小和依赖关系呈现出来,便于分析优化
// 安装
npm i --save-dev webpack-bundle-analyzer
在使用时,将其以插件的形式引入
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
构建过程优化
优化loader配置
loader处理文件的转换操作是很耗时的,所以需要让尽可能少的文件被loader处理
以babel-loader为例,使用include/exclude来避免不必要的转译,并开启缓存提升babel-loader的工作效率
- 在include/exclude中使用绝对路径,更倾向于使用include
- 在babel-loader中开启缓存 ?cacheDirectory=true
const path = require('path')
// ...
module: {
rules: [
{
test: /\.js[x]$/,
//
include: [path.resolve(__dirname, 'src')],
use: {
loader: 'babel-loader?cacheDirectory=true',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
特殊照顾第三方库
第三方库一般存放于node_module中,对于它们的理想处理方式是,只在第一次打包时对其进行处理,以后只要当前依赖包的版本未发生变化就无须重新打包,推荐大家使用DllPlugin
使用DllPlugin处理文件,主要分为以下两步:
- 基于dll专属的配置文件webpack.config.dll.js,打包dll库
- 基于webpack.config.js文件,打包业务代码
dll配置文件编写如下:
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
// 依赖包数组
vender: [
'prop-types',
'babel-polyfill',
'react',
'react-dom',
'react-router-dom'
]
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
library: '[name]_[hash]'
},
plugins: [
new webpack.DllPlugin({
// DllPlugin的name属性要和library保持一致
name: '[name]_[hash]',
path: path.join(__dirname, 'dist', '[name]-manifest.json'),
// context需要和webpack.config.js保持一致
context: __dirname
})
]
}
之后在webpack.config.js中针对dll稍作配置:
const path = require('path')
const webpack = require('webpack')
module.exports = {
mode: 'production',
// 编译入口
entry: {
main: './src/index.js'
},
// 目标文件
output: {
path: path.join(__dirname, 'dist/'),
filename: '[name].js'
},
// dll相关配置
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
// manifest就是我们第一步中打包出来的json文件
manifest: require('./dist/vendor-manifest.json')
})
]
}
减少一核工作多核围观的情况-Happypack
因webpack是单线程,即使此刻存在多个任务等待处理,也需要排队一个一个来,这是单线程的缺点,好在CPU是多核的,使用Happypack会充分利用CPU的多核优势,将多个任务分解为多个子进程去并发执行,提高打包效率
注: 当项目比较小时不建议使用Happypack进行配置
Happypack的使用很简单,只需将对应的loader配置转移到Happypack中就可以,在此之前需要告知Happypack我们需要多少并发的进程:
// 先安装happypack
npm i -D happypack
const Happypack = require('happypack')
// 手动创建进程池
const happyThreadPool = Happypack.ThreadPool({size: os.cpus().length})
module.exports = {
module: {
rules: [
...
{
text: /\.js$/,
// ?后面的查询参数指定了处理这类文件的Happypack实例的名字,唯一
loader: 'happypack/loader?id=happyBabel',
...
}
],
},
plugins: [
...
new Happypack({
// 此处id的名字和上面的查询参数必须相对应
id: 'happyBabel',
// 指定线程池
threadPool: happyThreadPool,
loaders: ['babel-loader?cacheDirectory']
})
]
}
图片优化
当前比较流行的图片格式有JPEG/JPG、PNG、Base64、SVG、Webp等,还有精灵图(CSS Sprites)至今仍还有使用,下面主要介绍一下其特性和应用场景
JPEG/JPG
特性: 有损压缩、体积小、不支持透明
优点
- 通用,大多数浏览器都支持的图片格式
- 压缩算法高效,图片格式体积小
- 以24位存储单个图可呈现1600多万中颜色,色彩丰富
不足
- 图片颜色对比强烈,压缩后导致图片模糊
- 不支持透明度
应用场景
JPG适用于呈现色彩丰富的图片,即使是大图,其体积也比较小
- 页面背景图
- 轮播图
- 电商中商品展示图
PNG 8 / PNG 24
特性: 支持透明、无损压缩、体积大
优点
- 支持透明
- 无损压缩的高保真图片格式
- PNG 8最多支持256种颜色
- PNG 24最多支持1600万中颜色
- 色彩表现力更强,对线条轮廓处理更细腻
不足
- 体积大
在项目中,不推荐使用PNG处理颜色复杂的图片,优先选用PNG8格式
应用场景
- 一些小图-logo
- 颜色简单,对比明显的图片
- 支持透明
Base64
将一张图片数据编码成一串字符串,使用该字符串代替图像地址url
特性: 减少http请求、需要编码、小图标使用
优点
- 将图片进行Base64编码,可以直接将结果写入html/css
- 减少http请求
不足
- 增加html/css文件体积
- 解析时间增加
- Base64 编码后,图片大小会膨胀30%左右
应用场景
- 图片实际尺寸很小(<2kb)
- 图片无法以雪碧图的形式与其它小图结合
- 图片更新频率低
SVG
特性: 体积小、不失真、兼容好
可缩放矢量图形(Scalable Vector Graphics),一种 XML 应用,以一种简洁、可移植的形式表示图形信息
优点
- 图片可无限放大缩小不失真
- 文件体积更小
- 压缩性强
不足
- 浏览器的渲染成本高
- 与其它格式的图片相比,学习成本高
应用
- 直接放在html中,成为DOM的一部分
<body> <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> <circle cx="40" cy=45" r="50" /> </svg> </body>
- 以 .svg格式存储,引入html中
<img src="./xx/文件名.svg" />
WebP
特性: 集多家优点于一身,全能选手
是Google专为Web开发的一种加快图片加载速度的图片格式
优点
- 支持透明
- 可以显示动图
- 显示图形丰富
不足
- 增加服务器的负担
- 兼容问题
待续