大型复杂前端项目的优化方法前言
对于前端来说,如果是写一个简单的页面,其实在写的过程中很少考虑性能优化和错误监控。 因为就某一页而言,不会影响整个项目。 但就大型协作前端项目而言,如果大家都不考虑性能优化,较轻的问题就是:页面卡顿、操作不流畅。 比较严重的问题是:页面卡顿、内存崩溃、各种报警。 因此,基于理解、实践和收集,我收集了一些大型前端项目中的性能优化方法,供大家分享。 如有错误请指正~
1. 实施优化 1.1 容器化
所谓容器是指打开不同文档时,不加载重复的代码,只加载不同的文档数据。 容器化有两种实现思路:
1.在客户的帮助下
典型场景:列表页-->选中文档-->打开文档
用户选择一个文档并单击链接打开它后,实际上并不会打开一个新文档,而是会切换它。 APP会一直保留一个(代码已加载,是一个文档容器)。 这时,文档就会将链接交给容器,容器只需要拉取相应的数据,不需要重新加载新的代码。
2.将代码收集到一页(SPA解决方案)
典型场景:列表页-->选中文档-->打开文档
当列表页面打开时,相应的文档容器代码被快速延迟加载。 当用户点击指定文档时,将不再打开新页面,而只会对当前页面进行容器切换。 用户返回列表页面并且仍在当前页面。
1.2 离线缓存
离线缓存处理的核心难点在于离线数据的处理。 目前离线资源处理有两种选择:
1.在客户的帮助下
典型场景:打开列表页面。
当打开网页请求html/js/css/时,APP会拦截网络请求,检查是否命中本地资源。 如果命中本地资源,则直接返回。 在这种场景下,需要注意的是,客户端需要建立代码更新机制来更新离线代码包。
2.
典型场景:打开列表页面。
资源注册后即可使用。 但必须使用https协议,而且还有ios兼容性问题。
可以参考这篇文章:前端离线探索()
1.3 数据序列化
典型场景:打开文档拉取后台数据时。
数据序列化意味着对数据结构或对象进行编码,然后通过网络传输。
常见的协议有XML/JSON/PB等,对于大文档来说,使用xml或者json来传输数据过于冗余。 但如果使用过于复杂的压缩算法,比如Pako.js,就会增加解压时间,得不偿失,所以找到合适的数据序列化方法至关重要。
1.4 按需加载 1.4.1 读写分离
典型场景:打开文档时拉取文档代码。
读写分离也是按需加载的一种形式。
读写分离意味着:区分用户。 并非所有用户都需要加载所有代码。 读用户只需要加载读代码,写用户只需要读写代码。 这对于阅读用户来说更加友好。 。
1.4.2 点播介绍
典型场景:开发时引入第三方库。
按需导入意味着仅引用您需要的代码。
按需引入在开发中常常被忽视。 比如只需要一个功能,却引入了整个工具库。 '',实际上增加了更多冗余代码。 这可以通过引入一个函数来纠正:“.”。
1.5 延迟加载
典型场景:文档中一些不太常用的功能。
延迟加载是指:等待一些功能代码在需要时才被加载。
延迟加载对于第一个屏幕打开时间很有好处。 对于文档中一些不常用的功能,用户每次打开都要加载相应的代码,无疑是多余的。 通过拆分代码,等到用户需要或者点击的时候,再加载对应的代码。
1.6 使用网络
典型场景:当打开文档时,里面有大量复杂的计算。
网页在后台运行,不会影响页面的性能。
web无疑是解决js计算能力弱的一大利器。 如果将文档中复杂的计算放在主线程中,不仅页面会卡顿,而且打开复杂文档时还很可能崩溃。 将计算移至Web中,并以事件回调的形式返回计算结果,可以让用户使用起来更加顺手。
1.7 正则表达式
典型场景:匹配文档中的函数或文本时。
糟糕的正则表达式很容易导致性能问题。 核心问题是js的正则匹配是基于NFA的,很容易造成回溯问题。
常规引擎分为NFA(非确定性有限状态自动机)和DFA(确定性有限状态自动机)。
DFA 对于任何给定的状态和输入字符,DFA 只会转移到某个状态。 并且 DFA 不允许在没有输入字符的情况下进行状态转换。 对于任何状态和输入字符,NFA可以转移的状态都是非空集。 模糊匹配、贪婪匹配、惰性匹配都会造成回溯问题。 典型的正则匹配(例如 (.\*)+\d)将回溯。 那么在正常发育过程中,
正则表达式越精确越好
切换到 DFA 的常规引擎
详细请参考:浅谈正则表达式原理
1.8 使用缓存
典型场景:一堆非常耗时的数据,每次更新都需要重新计算,但更新频率不高。
使用缓存可以有效减少搜索时间。
js引擎是单线程的,大量复杂耗时的计算会导致页面卡顿。 这可以通过将计算移至计算中间或缓存不经常更新的计算来完成。
function complexCompute(){
let result;
...
return result;
}
对于这种复杂的计算,如果每次需要用到数据时都要进行计算,那就得不偿失了。 缓存可以有效减少计算次数。
// 加入缓存
let cache;
function complexCompute(){
let result;
...
cache = result;
return result;
}
function getResult (){
// 有更新的时候再进行计算
if(update){
complexCompute();
}
return cache;
}
这样只有在更新时才会进行计算。
1.9 索引
典型场景:查找一堆复杂的数据,并且每个数据都有唯一的key。
建立索引可以快速提高搜索速度。 例如,一堆数据存储在一个数组中。
const persons = [
{ name: "zhangsan", age: 12 },
{ name: "lisi", age: 11 },
];
并存储为对象。
const persons = {
zhangsan: {
age: 12,
},
lisi: {
age: 11,
},
};
搜索年龄,那么使用数组搜索的时间复杂度为o(N),使用对象的时间复杂度为o(1)。
2. 架构优化 2.1 设计模式的六大基本原则
五原则一法则:
2.2 组件库
其核心含义是代码复用。 功能比较单一或独立。 它处于整个系统代码级别的最底层,依赖于其他代码。 比如我们常用的一些UI组件、逻辑组件等。
该组件的核心是:
多功能性
与业务无关
兼容性
通过将一些常用的UI和逻辑拆分成组件,不仅可以使代码更加简洁、更易于维护,而且具有高内聚、低耦合的特点。
2.3 学习管理
当大型项目库的代码量急剧增加时,管理就成了一件麻烦的事情。 为了方便代码共享,需要将代码库拆分成独立的包。 Lerna 是一个用于优化和管理 JS 多包项目的强大工具。
典型的目录如下所示:
base/package.json;
packages/package-1/ package.json;
package-2/package.json;
2.4 享元模式
典型场景:Excel规范。
享元模式( mode)主要用于减少创建对象的数量,以减少内存占用,提高性能。 这类设计模式是一种结构模式,它提供了一种减少对象数量从而改善应用程序所需的对象结构的方法。
我国对数据的管理基本上是采用享元模型来实现的。
例如,一个文档中有 1,000 个相同的字符串。
Excel 并不真正存储 1000 个字符串。 它只会存储一个字符串。
具体来说,每个格子存储该字符的下标索引。
这无疑大大节省了存储空间。
3. 构建优化 3.1 打包优化 3.1.1 公共包
它用于提取第三方库和公共模块,例如以下文件。
改进并替换为 . 问题是:它将所有公共包合并为一个,但并非所有子模块都需要其中的所有内容。
与父子关系的思想不同,引入了概念,在入口chunk和异步chunk中查找重用的模块,并以-chunk的形式将重叠的模块分开,即有可能是多个块。 它进一步限于所有块中通用的模块。
它开箱即用,常见配置如下:
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 6,
maxInitialRequests: 4,
automaticNameDelimiter: '~',
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
}
}
}
}
可以制定更详细的规则。
3.1.2 动态导入( )
通过动态导入实现延迟加载和代码分离,可以用来获取更小的资源,控制资源加载优先级。 如果使用得当,将会极大地影响加载时间。
当谈到动态代码分割时,提供了两种类似的技术。 对于动态导入,第一种也是首选的方法是使用建议的 () 语法。 如下:
return import(/* webpackChunkName: "lodash" */ "lodash").then((_) => {
_.join(["Hello", "webpack"]);
});
3.1.3 树-
树 - 通常用于描述删除上下文中的死代码。 例如:
索引.js
import { cube } from "./math.js";
console.log(cube(5));
立方体.js
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
其中cube.js中的都是未引用的代码,打包时不需要。 通过在依赖包的.json中写入:
"sideEffects": false,
您可以打开依赖包的树。
但是使用tree-有三个规则。
使用模块语法(即 和 )。
在.json 文件中,添加“”条目。
介绍一个可以去除死代码的压缩工具(例如)。
具体的例子可以看这个。