1、基于 Proxy 的 reactivity
vue2响应式:
对象:通过defineProperty对对象的已有属性读取和修改进行劫持(监视/拦截)。
数组:通过重写数组原型的七种方法,实现对数组更改的劫持来处理数组响应式。
push、pop、shift、unshift、splice、sort、reverse。
缺点:
对象直接新添加的属性或删除已有属性, 界面不会自动更新;
直接通过下标替换数组元素或更新数组length, 界面不会自动更新;
通过Vue.set()处理实现响应式
对象:Vue.set(this.obj, "name", "chengqiang");
数组:Vue.set(this.list, "0", "chengqiang");
vue3响应式:
通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的增删改查,代理对象中拦截对数据赋值的操作,从而获知数据变化。
通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作。
const dataObj = {};
const dataProxy = new Proxy(dataObj, {
get(target, key){
// 收集依赖
Reflact.get(target, key);
},
set(target, key, value){
// 进行对应界面的更新
Reflact.set(target, key, value);
}
});
2、tree-shaking
Vue 2 的体积其实已经不算大了,但是使用 Vue 3 时可以让打包体积更小。这一方面利益于 Composition API 的设计,另一方面也是因为随着 ESM 模块化规范(和 TS 模块化规范)的成熟,tree-shaking 被使用得越来越广泛了。
tree-shaking 是指针对代码中模块的依赖关系进行静态分析,将完全没有使用到的模块在打包时直接移除掉,只打包使用到的模块,从而减少体积。
得益于 Vue 3 良好的模块划分,开发者在使用 Vue 3 时可以按需选择需要的模块引入,而不用一次性将所有的代码全部引入,这样在打包时 Vue 3 中没有被引用的源码将被移除。
在 Vue 的仓库中专门有一个 @vue/size-check 的包,就是用来测试 tree-shaking 的:
import { h, createApp } from '@vue/runtime-dom'
// The bare minimum code required for rendering something to the screen
createApp({
render: () => h('div', 'hello world!')
}).mount('#app')
这个例子中只使用了 h 和 createApp,其它的诸如 watch、computed、reactive 等模块都没有被引用,因此打包时也将只包含使用到的源码,从而使整个应用的打包体积变得更小
3、渲染性能优化
vue2 diff算法:
虚拟 DOM 的更新需要对新、旧两棵 VNode 树进行全量遍历和对比,然后才能确定发生变动的地方。
diff 只进行同级比较
- 如果新节点有子节点而老节点没有子节点,则判断老节点是否有文本内容,如果有就清空老节点的文本内容,然后为其新增子节点。
- 如果新节点没有子节点而老节点有子节点,则先删除老节点的子节点,然后设置文本内容。
- 如果新节点没有子节点,老节点也没有子节点,则进行文本的比对,然后设置文本内容。
- 如果新节点有子节点,老节点也有子节点,则进行新老子节点的比对,然后进行新增、移动、删除的操作。
根据新老 vnode 子节点不同情况分别处理
Vue 3 diff算法性能优化:
以 v-if 和 v-for 作为边界,将模板分成很多 “块”,每个块中的结构是完全固定的,这样块中就不需要进行树的遍历,只需要对比绑定的值即可;
将静态节点、子树等渲染代码移到渲染函数之外,这样可以避免每次渲染时重新创建这些不会变化的对象;
动态数据节点标记(patchflag)节点类型,调用对应节点类型的比较方法,如:文本,属性调用各自的比较方法;
@监听函数的缓存,数据更新,dom事件拿缓存的事件。
将元素的更新类型进行细分,例如动态绑定的部分如果只涉及到 class,则在对比时只需要对比 class 即可,不需要对比它的内容。
4、模版编译
抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
vue2模版解析
vue模版结构:
<div class="box">
<h3 class="title">我是一个标题</h3>
</div>
抽象语法树:
{
tag: 'div',
attrs: [{name: 'class', value: 'box'}],
type: 1,
children: [
{
tag: 'h3',
attrs: [{name: 'class', value: 'title'}],
type: 1,
children: [
{text: '我是一个标题', type: 3}
],
}
]
}
vue框架中有一个compile,compile主要负责将模版转化为render函数,而render函数调用后得到虚拟dom树。
编译的过程分两步:
- 将模版字符串转化给AST
- 将AST转化为render函数
vue3模版解析
- 调用
baseParse()
方法,解析传入的模板,生成 AST(即ast
变量) - 调用
transform()
方法对 AST 进行转换 - 调用
generate()
方法,根据转换后的 AST 生成render()
方法并返回