5k字长文总结性能优化:构建你的前端知识体系

5,836 阅读15分钟

浏览器架构

第一阶段:单进程架构(2007年以前)

chrome-old-redactor.webp 指的是浏览器的所有功能模块都运行在一个进程里,包括网络线程、插件线程、JavaScript运行环境、渲染引擎和页面,如此多的模块都在一个进程,是浏览器不安全、不流畅、不稳定的主要原因

进程中的任意一个线程出问题会导致整个进程的崩溃,而插件线程容易崩溃;渲染引擎模块不稳定,复杂一点的JavaScript计算逻辑,比如无限循环,会独占整个线程,导致运行在该线程的其他模块难以执行,就会导致整个页面的崩溃;页面的内存泄露会使单进程变慢,一般来说,页面使用时间越长,内存占用越高,浏览器响应速度越慢。

第二阶段:多进程架构(2008年以后)

chrome-v3-refactor.webp 如图,一个浏览器的架构由浏览器主进程、渲染进程、网络进程、插件进程GPU进程组成,不同的进程通过IPC(进程间通信)传递数据

  • 浏览器主进程,负责页面的展示、用户交互和子进程管理
  • 渲染进程,负责HTML,CSS,Javascript的渲染与展示,默认一个chrome为一个tab页建立一个渲染进程,为了安全都运行在沙箱下
  • 网络进程,负责页面的网络资源加载
  • 插件进程,负责插件的运行,由于插件容易崩溃,需要独立一个插件进程隔离
  • GPU进程,负责3D CSS(GPU加速)

虽然多进程架构解决了单进程的不安全、不流畅、不稳定问题,也带来了新问题

  • 占用更多资源
  • 体系架构更复杂

大纲:

  1. 图片优化

  2. DOM优化

  3. css优化

  4. JavaScript优化

  5. 音视频优化

  6. 静态文件优化

  7. 打包构建优化

  8. 页面渲染优化

  9. 浏览器优化

  10. 接口优化

  11. 前端缓存

图片优化

图片格式对比

1. jpg是有损压缩,png是无损压缩位图,文件比gif和jpg大,但是图像质量更好;gif是位图图形格式,采用LZW压缩算法进行编码,只支持完全透明和完全不透明图形;webp是一种现代图像格式,支持无损压缩和有损压缩

2. jpg不适合线条图形和文字、图标图形,压缩算法不支持这些图形;gif不适合存储彩色图片,因为只有8像素;webp图像质量好,体积小,采用8位压缩算法,不适合彩色图片

3. 使用场景,jpg适合颜色丰富图片,不规则的图形,彩色图大焦点图;png适合纯色透明线条绘图,图标;边缘清晰、有大块相同的颜色区域;gif适合图标,动画;webp适用于图形和半透明图像

图体积优化

前端开发经常处理图片,图片体积过大会导致页面渲染速度减慢,有时甚至会出现白屏。对图片进行压缩处理,可以显著的提升页面加载速度,提升用户体验

常用的图片压缩工具:

用于png图片压缩

用于jpeg图片压缩

通过改变每帧的比例减少gif大小,同时可以使用透明达到更小的文件大小

简单、体积小,适用于多种图片格式

webpack压缩图片

  • 使用CompressionWebpackPlugin
  1. 安装依赖
$ npm install compression-webpack-plugin --save-dev
  1. 配置webpack
// webpack.prod.conf.js
const CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
  plugins: [
    new CompressionPlugin({
      filename(pathData) {
        if (/.svg$/.test(pathData.filename)) {
          return "assets/svg/[path][base].gz";
        }
        return "assets/png/[path][base].gz";
      },
  })],
};

具体用法参考

响应式图片: 主要适用于不同分辨率图片的正常展示

  1. css媒体查询
@media screen and(max-width:640px){
    my_image{width:640px}
}
  1. javascript绑定事件检测窗口大小
  2. img标签属性(x表示图像的设备像素比)
  <img srcset="img-320w.jpg,img-640w.jpg 2x,img-960w.jpg 3x" src="img-960w.jpg" alt="img"></img>

用户体验优化,逐步加载图片

  • 图片懒加载

使用统一占位符(加loading效果、骨架屏方案等)

  • 使用LQIP(低质量图像占位符)

加载先使用低分辨率图片,后使用高分辨率图片替换

  • 使用SQIP(基于svg的图像占位符)

图片服务器自动优化解密

DOM优化

概念

DOM是表述HTML的内部数据结构,它会将Web页面和JavaScript脚本连接起来,并过滤一些不安全的内容

DOM树生成过程

HTML文件解析器不是等整个文档加载完成后才解析的,而是网络进程加载了多少数据,HTML解析器便解析多少数据

dom-parser.webp 从字节流转换为DOM需要三个步骤

一、通过分词器将字节流转换为Token

解析HTML需要做词法分析,通过分词器将字节流转为一个个Token,分为tag token和文本token。tag token又分为start token和end token,比如<div>就是start token,</div>则是end token

二、将Token解析为DOM节点

HTML维护了一个token栈结构,用于计算节点之间的父子关系,在第一个阶段生成的token会按顺序压到这个栈中,具体的处理逻辑为:

  • 如果压到栈里的是startTag token,html解析器会为这个token创建一个dom节点,然后将该节点加到DOM树中,它的父节点就是栈中那个相邻元素生成的节点
  • 如果分词器解析出来的是文本token,会生成一个文本节点,将该节点安排在dom节点中,文本节点不需要压入栈中,它的父节点就是当前栈顶Token对应的DOM节点
  • 如果分词解析器解析出来的是Endtag token标签,比如endtag div,html解析器会查看token栈顶对应的元素是否是startTag div,如果是就将startTag div从栈中弹出,表示该div解析完成 通过分词器产生的新 Token 就这样不停地压栈和出栈,整个解析过程就这样一直持续下去,直到分词器将所有字节流分词完成

三、将DOM节点添加到DOM树中

控制DOM大小

页面交互卡顿和流畅度很大一部分原因在于页面有大量的DOM元素,比如在一个上万节点的DOM树上使用querySelectorAll或getElementByTagName方法查找节点,是一个耗时操作。另外元素绑定事件,事件冒泡和事件捕获也是耗时操作

通常控制DOM大小的操作包括:

  • 对DOM节点的操作统一处理后再加入到DOM Tree中
  • 尽量不使用DOM操作,可以使用DocumentFragment
const list = document.getElementById('#root')
const courses = ['Javascript', 'Vue', 'React', 'Electron']

const fragment = new DocumentFragment()

courses.forEach((course) => {
 const li = document.createElement('li')
 li.textContent = course
 fragment.appendChild(li)
})

list.appendChild(fragment)
  • 延迟加载即将呈现的内容

先将元素设置为display:none;DOM操作完成后再恢复显示

  • 删除DOM前要取消监听该DOM的事件

使用removeListener防止产生无法回收的内存

  • vue,react都有虚拟DOM,通过diff算法简化和减少DOM操作

html优化

  1. 减少html嵌套
  2. 减少DOM节点数
  3. 减少无意义代码
  4. 删除http或https
  5. 删除无意义的代码和注释
  6. 省略多余的标签和属性
  7. 使用相对路径
  8. css放页面头部,JavaScript放html底部

JavaScript的渲染、加载、解析会阻塞DOM,而CSS的加载不会阻塞DOM解析,但会阻塞DOM树渲染,也会阻塞js执行,css放在头部可以减少浏览器重排次数,如果放在底部,会出现白屏,影响用户体验

css优化

  • 减少css嵌套层数

css的解析顺序是从右到左

  • 使用class和id选择器,不使用元素选择器
  • 减少expensive属性的使用,如nth-child,position:fixed等
  • 避免使用占用cpu和内存较多的属性,比如text-indent
  • 减少使用耗电量多的属性,比如css3 3d transform
  • 尽量不使用css表达式
  • 渲染动画时开启CPU加速
  • 减少回流和重绘

减少回流和重绘

  • 使用absolute定位,使元素脱离文档流
  • 避免使用table布局
  • 尽量不使用float布局
  • 图片设置width和height
  • 设置屏幕缩放级别用viewport
  • 避免频繁设置样式,最好把样式设置完成后再一次性更改
  • 简化浏览器不必要的任务,减少重新布局
  • 避免使用css表达式
  • 避免使用会引起回流/重绘的属性,把相应变量缓存起来
  • 合并对多次DOM的操作,改为批量操作
  • 减少绘制区域范围,减少绘制开销大的属性的使用

提升css文件加载性能

  1. 使用外链css
  2. 尽量避免使用@import

iconfont层面

  1. 字体部署在CDN上
  2. 将字体以base64形式保存在css中并使用localStorage进行缓存

JavaScript优化

当需要时才优化,从代码团队规范层面考虑可维护性

  • 使用id选择器,性能最优,因为DOM数相对来说更少
  • js函数保持简洁,单一职责原则
  • 使用事件委托
  • 防抖、节流处理
  • 减少js动画,使用css动画和canvas动画
  • 合理使用requestAnimationFrame代替setTimeout和setInterval
  • 合理使用缓存

cookie/sessionStorage/localStorage/indexedDB

  • 长列表使用虚拟列表优化
  • 使用可缓存的ajax请求
  • 如果js文件中没有DOM操作,可以将JavaScript脚本设置为异步加载,用async或defer标记代码
<script async type="text/javascript" src="test.js"></script>
<script defer type="text/javascript" src="test.js"></script>

async一旦加载完成就会立即执行,使用了defer标记的脚本文件。需要在DOMContentLoaded事件之前执行

nginx开启http2

  1. 升级openSSL
  • $ openssl version 2.重新编译
  • $ cd nginx-xxx
  • $ ./configure --with-http_ssl_module --with-http_v2_module
  • $ make && make install
  1. 验证HTTP2 network查看protocol是否为h2

音视频优化

  1. 音视频压缩

分为无损压缩和有损压缩,常用的工具有:

  1. 使用合适的音视频格式

image.png 3. 视频懒加载

设置preload为none,浏览器不会预加载任何视频数据

<video controls preload="none">
  <source src="路径.webm" type="video/webm"></source>
  <source src="路径.mp4" type="video/mp4"></source>
</video>
  1. 移除多余音轨信息

音轨: 在音序器软件中看到的一条一条的平行“轨道”。每条音轨分别定义了该条音轨的属性,如音轨的音色,音色库,通道数,输入/输出端口,音量等。

声道:指声音在录制或播放时在不同空间位置采集或回放的相互独立的音频信号,所以声道数也就是声音录制时的音源数量或回放时相应的扬声器数量。

增加muted属性,有助于减少视频体积

<video muted></video>
  1. 移除不必要的视频

在不需要使用视频的时候去掉视频,比如小屏幕可以通过媒体查询去掉视频展示

@media screen and(max-width:650px){
  #video{
      display:none;
  }
}

静态文件优化

压缩工具

html-minifier、clean-css、uglify-js、uglify-es

静态文件打包

  • 公共组件拆分
  • 压缩html、css、js、图片
  • 合并css/js文件,雪碧图

打包构建工具优化

打包工具

  • Grunt,最早的打包工具,一个项目需要定制多个小任务和引用多个插件
  • Gulp,通过流来简化多个任务的配置和输出,配置代码较少
  • Webpack,预编译,文件在内存中处理,支持多种模块化(commonjs,amd,umd),配置简单
  • Vite,esm编译

打包构建优化以webpack为例

  • 升级版本到webpack5

利用最新版本的长期缓存,持久性缓存提升构建性能、更好的tree shaking和代码生成改善包大小,升级具体参考官方文档迁移指南

  • 缩小文件搜索范围

配置loader时通过include命中文件夹,缩小文件搜索范围

const path = require("path")
const os = require("os")

const cacheLoader={
  loader:'cache-loader'
}
const threadLoader={
  loader:'thread-loader',
  options:{
    workers:os.cpus().length-1
  }
}

function resolve(dir){
  return path.join(__dirname,'..',dir)
}
module.exports={
    ...,
    modules:{
        rules:[
        {
            test:/\.js$/,
            use:[
                cacheLoader,
                threadLoader,
                {
                  loader:'babel-loader',
                  options:{
                    cacheDirectory:true
                  }
                }
            ],
            include:[resolve('src')]
        }
        ]
    }
}

通过resolve.extensions配置后缀列表

resolve:{
  extensions:['.js','.vue','.json']
}

当遇到require('./test')这样的导入语句时,webpack会按顺序查找./test.js文件,找不到就查找./test.vue文件,再找不到会查找./test.json文件,最后找不到就报错,写的时候要保证以下几点:

  • 后缀列表要尽可能的短

  • 频率越高的文件后缀要写在最前面,保证最快速度找到文件

  • 写入导入语句时,最好要带上后缀,可以避免以上的查找过程

  • 多进程、多线程的打包

示例代码在上面,webpack5默认开启多进程和缓存,无需引入外部插件;thread-loader开启多线程打包,webpack5后不推荐使用happypack

  • 区分环境

webpack4开始,通过mode参数区分开发、生产环境

  • 压缩代码

包括HTML,CSS,Javascript,图片字体等资源的压缩

css可以使用extract-text-webpack-plugin插件,该插件用于从js中提取css代码,注意不能与style-loader共用,一般style-loader用于开发环境,extract-text-webpack-plugin用于生产环境

{
  test:/\.(png|jpe?g|gif|svg)(\?.*)?$/,
  type:'asset/resource',
  generator:{
    filename:utils.assetsPath('img/[name].[contenthash:8].[ext]')
  }
}
  • 使用tree shaking

tree shaking可以帮助移除项目中的无用代码,通过package.json的sideEffects属性实现

  • 开启bundleAnalyzer

使用webpack-bundle-analyzer

安装依赖

npm i webpack-bundle-analyzer -D

文件引用

//webpack.prod.conf.js
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin

plugins:[
    new BundleAnalyzerPlugin()
]

npm run build开始构建,会生成文件依赖图

  • 分包加载

通过splitChunks配置实现

optimization:{
  moduleIds:'deterministic',
  concatenateModules:true,
  runtimeChunk:{
    name:'manifest'
  },
  splitChunks:{
    cacheGroups:{
      defaultVendors:{
        idHint:'chunk-vendors',
        test:/[\\/]node_modules[\\/]/,
        priority:-10,
        chunks:'initial'
      },
      echarts:{
        name:'chunk-echarts',
        test:/[\\/]node_modules[\\/](vue-)?echarts[\\/]/,
        priority:-9,
        chunks:'all',
        reuseExistingChunk:true,
        enforce:true
      }
    },
  }
}

打包后会生成manifest、chunk-echarts命名开头的js文件

  • 开启HMR

通过webpack-dev-server的hot属性实现

  • 使用CDN

CDN又叫内容分发网络,通过把资源部署到不同的城市,用户在访问时优先访问地理位置最近的服务器,从而加速资源获取速度

一般静态资源适合放在CDN

  • 设置devtool

不同的devtool存在性能差异,开发环境最佳为eval-cheap-module-source-map

浏览器优化

webview启动过程: 在app首次打开时没有启动浏览器内核的,只有在创建webview内核的时候,才会启动浏览器内核(70-700ms),并创建webview的基础框架

优化

  • 使用全局webview优化,在客户端刚启动的时候就创建一个全局webview待用,隐藏。当用户访问webview时,直接用webview加载对应网页并显示

减少首次打开webview的时间,但会增加内存开销

  • url预加载,从所有准备好再请求页面到准备和请求页面同时进行,URL load和动画并行加载

接口优化

  1. 接口合并,减少页面请求数
  2. 接口上CDN,可以把不需要实时更新的接口同步至CDN,等此接口内容变更后再同步至CDN集群中,如果一段时间内未请求数据,会用源站接口继续请求
  3. 接口域名上CDN,增强可用性、稳定性
  4. 接口降级,核心业务用接口降级,用基础接口进行实现,比如大促的推荐接口,在大促时间点可以直接运营编辑的数据。另外接口如果无法访问,使用预设好的垫底备份数据。
  5. 接口监控,监控接口成功率,比如弱网、超时、网络异常、网络切换等情况,排查出问题要结合后端、测试运维一起解决
  6. 接口监控

接口缓存优化策略

  1. ajax/fetch缓存

前端请求时带上cache,依赖浏览器本身缓存机制

  1. localstorage、sessionstorage本地缓存
  2. 多次请求,在弱网、超时等异常情况需要

同构直出

一套代码既可以在服务端运行又可以在客户端运行,这就是同构

优点

  • 性能:降低首屏渲染速度
  • 服务端渲染在SEO层面有天然优势
  • 可以有效避免客户端兼容性问题,比如白屏
  • 直接上线2个版本,便于灾备

技术实现

  • next.js

服务端渲染react框架

  • gatsbyjs

服务端react框架

  • nuxt.js

服务端渲染vue框架,vue采用vue-server-renderer调用renderToString()方法

交互方式

  • 后端专注于业务功能实现和API封装
  • 前端负责实现页面前端交互,根据后端API拼接前端模板,页面渲染以及服务器维护
  • 前端需要处理Node server的机器环境、代码部署、容灾、日志、监控等以往后端人员需要具备的运维知识,前端人员的综合能力要求比以往更高
  • 前端项目开发周期变长了,需要事先和产品、运营沟通排期问题 前端需要具备全栈工程师能力

自动化测试

  • UI自动化 上手简单,稳定性较差,工具有appium、robot framework、selenium、airtest等
  • 接口自动化 稳定,性价比高,工具有httpRunner、JMeter、Python+requests、Java+restassured
  • 单元测试 性价比极高,一般由开发完成,工具有mocha、jest

自动化部署上线

步骤

  1. 拉取代码库仓库
  2. 自动化工具在线打包编译
  3. 代码上线部署至灰度机器
  4. 代码上线部署至全量机器
  5. CDN后台静态文件更新缓存

上线前质量检测

  • 白屏检测
  • js报错
  • 接口报错
  • 线上环境检查

页面性能

  • 页面完全加载时间检查
  • 前端html、css、js压缩检测
  • 前端大html、css、js、图片检测
  • 前端js、css个数检测
  • 服务器gzip检测
  • 服务器缓存设置时间检测

页面安全

  • HTTP和HTTPS检测
  • XSS检测
  • CSRF检测
  • 中间人工具检测

上线后质量检测

页面性能监控

  • js错误监控
  • API接口监控(接口是否超时、报错)
  • 日志详情(访问页面、页面停留时间)
  • 用户轨迹

上线后H5性能和错误监控

  • 统计报表 大盘走势、地域、运营商、浏览器
  • 页面管理
  • 性能指标
  • 报警服务

发版后app性能和错误监控

  • 网络请求
  • 启动监控(冷启动、热启动)
  • 崩溃监控(不同机型)
  • 页面监控(性能指标,FCP)
  • 网络监控(4G,3G,WIFI,断网)
  • Webview监控
  • 报警服务(网络异常)

前端缓存

前端缓存这一块神三元的文章已经说的很详细了,所以我不再赘述,上链接 浏览器灵魂之问,请问你能接得住几个?