Vue.js 从 1.x 到 2.0 版本,最大的升级就是引入了虚拟 DOM 的概念,它为后续做服务端渲染以及跨端框架 Weex 提供了基础。本文主要讲的是vue2到vue3.0的优化点。
vue2.x的痛点
- 源码自身的维护性;
- 数据量大后带来的渲染和更新的性能问题;
- 一些想舍弃但为了兼容一直保留的鸡肋 API 等;
- TypeScript 支持;
优化点
一、使用 monorepo管理源码
monorepo 把这些模块拆分到不同的 package 中,每个 package 有各自的 API、类型定义和测试。这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,开发人员也更容易阅读、理解和更改所有模块源码,提高代码的可维护性。
源码目录
vue-next
├─scripts
| ├─build.js
| ├─dev.js
| ├─utils.js
├─packages
| ├─global.d.ts
| ├─vue
| ├─template-explorer
| ├─size-check
| ├─shared
| ├─server-renderer
| ├─runtime-dom
| ├─runtime-core
| ├─reactivity
| ├─compiler-ssr
| ├─compiler-sfc
| ├─compiler-dom
| ├─compiler-core
├─test-dts
- reactivity:响应式系统
- runtime-core:与平台无关的运行时核心 (可以创建针对特定平台的运行时 - 自定义渲染器)
- runtime-dom: 针对浏览器的运行时。包括DOM API,属性,事件处理等
- runtime-test:用于测试
- server-renderer:用于服务器端渲染
- compiler-core:与平台无关的编译器核心
- compiler-dom: 针对浏览器的编译模块
- compiler-ssr: 针对服务端渲染的编译模块
- template-explorer:用于调试编译器输出的开发工具shared:多个包之间共享的内容vue:完整版本,包括运行时和编译器
二、使用 TypeScript 开发源码
从vue2中使用的Flow换成了Typescript
三、性能优化
1. 源码体积优化
- 首先,移除一些冷门的 feature(比如 filter、inline-template 等);
- 其次,引入
tree-shaking
的技术,减少打包体积。
tree-shaking
依赖 ES2015 模块语法的静态结构(即 import 和 export),通过编译阶段的静态分析,找到没有引入的模块并打上标记。然后压缩阶段会利用例如 uglify-js、terser 等压缩工具真正地删除这些没有用到的代码。
例如,如果你在项目中没有引入 Transition、KeepAlive 等组件,那么它们对应的代码就不会打包,这样也就间接达到了减少项目引入的 Vue.js 包体积的目的。
2. 数据劫持优化Proxy
Object.defineProperty
切换成 es6的Proxy
实现
Proxy 劫持了我们对 observed 对象的一些操作,比如:
- 访问对象属性会触发 get 函数;
- 设置对象属性会触发 set 函数;
- 删除对象属性会触发 deleteProperty 函数;
- in 操作符会触发 has 函数;
- 通过 Object.getOwnPropertyNames 访问对象属性名会触发 ownKeys 函数。
Proxy 劫持的是对象本身,并不能劫持子对象的变化,这点和 Object.defineProperty API 一致。但是 Object.defineProperty 是在初始化阶段,即定义劫持对象的时候就已经递归执行了,而 Proxy 是在对象属性被访问的时候才递归执行下一步 reactive,这其实是一种延时定义子对象响应式的实现,在性能上会有较大的提升。
3. 编译优化
-
Vue.js 2.x 的数据更新并触发重新渲染的粒度是组件级的,虽然 Vue 能保证触发更新的组件最小化,但在单个组件内部依然需要遍历该组件的整个 vnode 树。
-
vue3做到动静分离,执行diff时仅对比动态节点。
-
除此之外,Vue.js 3.0 在编译阶段还包含了对 Slot 的编译优化、事件侦听函数的缓存优化,并且在运行时重写了 diff 算法。
4. diff算法优化
深度递归遍历vnode树,节点的标签和key相同认为是同一个节点则更新,不同则删除,然后处理子节点。
子节点分这几种情况处理:纯文本、vnode 数组和空
- 空往往意味着添加或删除;
- 纯文本相同直接更新innerText,不同则删除;
- 新旧子节点都是vnode数组则diff算法来处理;
vue3.0 diff算法思想
- 编译模版时进行
静态分析
,标记动态节点
,diff对比差异时仅对比动态节点(性能提升明显); - diff算法先
去头去尾
,借此缩短遍历对比数组长度(对数组插入和删除操作性能优化明显); - 通过对更新前后子节点数组
建立映射表
的方式,将O(n^2)复杂度的遍历降低到O(n); - 通过
最长递增子序列
方法了来diff前后的子节点数组,减少移动操作的次数;
- vue3.0 diff算法的实现见demo演示
- 最长递增子序列算法实现:
/*
* 寻找最长递增子序列
* 使用动态规划思想,a -> c = a -> b + b -> c
* 其中p数组存储的是从p[p[i]] 到 p[i] 的最长递增子序列索引,也就是前一个b的索引;
* r数组存储最后一个元素也就是c的索引
*/
function getSequenceOfLIS(arr) {
const p = [0];
const result = [0];
for (let i = 0; i < arr.length; i ++) {
const val = arr[i];
const top = result[result.length - 1];
if (arr[top] < val) {
p[i] = top;
result.push(i);
continue;
}
// 二分法搜索
let l = 0, r = result.length - 1;
while(l < r) {
const c = (l + r) >> 1;
if (arr[result[c]] < val) {
l = c + 1;
} else {
r = c;
}
}
if (val < arr[result[l]]) {
if (l > 0) {
p[i] = result[l - 1]
}
result[l] = i;
}
}
// 回朔p数组,找出最长递增子序列
let preIndex = result[result.length - 1];
for (let i = result.length - 1; i > 0; i --) {
result[i] = preIndex;
preIndex = p[preIndex]
}
return result;
}
四、语法 API 优化:Composition API
1. 优化逻辑组织
编写组件本质就是在编写一个“包含了描述组件选项的对象”,我们把它称为 Options API,符合直觉思维。
Options API
的设计是按照 methods、computed、data、props 这些不同的选项分类,当组件小的时候,这种分类方式一目了然;但是在大型组件中,一个组件可能有多个逻辑关注点,当使用 Options API 的时候,每一个关注点都有自己的 Options,如果需要修改一个逻辑点关注点,就需要在单个文件中不断上下切换和寻找。
Composition API
将某个逻辑关注点相关的代码全都放在一个函数里,这样当需要修改一个功能时,就不再需要在文件中跳来跳去。
2. 优化逻辑复用
- vue2 有mixin 变量命名容易冲突的问题
- Composition API 显示的将变量引入当前组件,解决明明冲突问题
- 简单组件使用Options API,复杂组件使用 Composition API
建议
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!