# vue编译原理
1、@vue/compiler-core解析
2、前沿发展方向:fizz流式渲染、svelte编译框架
3、编译原理入门:the super tiny compiler (opens new window)
4、正则表达式可视化工具:可视化工具 (opens new window)
5、 vue3模版转译工具:vue3 template explorer (opens new window)
# 1、思考点
- 哪些是vue2.x的性能提升点?
- diff:diff优化如何更快,算法已很难优化
- 将diff算法拆出,放在worker执行,另起线程(preact)
- 重编译,轻运行时,参考svelte框架,编译优化处理动静结合页面可获取最大收益
- proxy:使用proxy比object.defineProperty好
- API功能优势可代理数组和对象
- 代理返回对象,使用函数式编程,挂载至this的属性减少,优化性能
- ssr更多的利用静态字符串拼接减少动态处理,来优化性能
# 2、编译原理
- 常见场景有两种:纯静态页面和动静结合页面,vue2.0全部需走diff。
- vue3的优化地方:业务中绝大部分为动静结合的页面,对该类型页面进行编译优化处理可以获取最大的收益。
总体思想:分治思想-分而治之,将大的,复杂的问题拆解成若干类似的小问题
<!-- 纯静态页面 -->
<div>
<h1>hello world!</h1>
</div>
<!-- 动静结合页面 -->
<div>
<h1>hello world!</h1>
<p>{{text}}</p>
</div>
2
3
4
5
6
7
8
9
# 1、动静分离处理
- 使用patchFlag进行Vnode标记,记录具体动态属性,实现靶向更新。
- 添加dynamicChildren数组,提取动态节点存储,使用block tree进行优化
export const enum PatchFlags {
TEXT = 1,// 动态的文本节点
CLASS = 1 << 1, // 2 动态的 class
STYLE = 1 << 2, // 4 动态的 style
PROPS = 1 << 3, // 8 动态属性,不包括类名和样式
FULL_PROPS = 1 << 4, // 16 动态 key,当 key 变化时需要完整的 diff 算法做比较
HYDRATE_EVENTS = 1 << 5, // 32 表示带有事件监听器的节点
STABLE_FRAGMENT = 1 << 6, // 64 一个不会改变子节点顺序的 Fragment
KEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性的 Fragment
UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 Fragment
NEED_PATCH = 1 << 9, // 512
DYNAMIC_SLOTS = 1 << 10, // 动态 solt
HOISTED = -1, // 特殊标志是负整数表示永远不会用作 diff
BAIL = -2 // 一个特殊的标志,指代差异算法
}
// like fiber!
// Block - Block Tree
{
tag: 'div',
children: [
{ tag: 'h1', children: 'hello world!' },
{ tag: 'p', children: ctx.text, patchFlag: 1 /* TEXT */ },
],
dynamicChildren: [
// 所有子元素中动态的部分哦
{ tag: 'p', children: ctx.text, patchFlag: 1 /* TEXT */ },
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
问题1:若只提取动态节点,与原dom节点不对等,diff存在问题,例如v-if、v-for
解决方法:使用block tree解决,尽量保持一样的结构
// demo1
<div>
<h1>hello world!</h1>
<div v-if="someCondition">
<p>{{ text }}</p>
</div>
<section v-else>
<p>{{ text }}</p>
</section>
</div>
// demo2
<div>
<p v-for="i in list">{{ i }}</p>
</div>
//尽量保持一样的结构,使用block tree解决
{
tag: 'div',
children: [
{ tag: 'h1', children: 'hello world!' },
// ...
],
dynamicChildren: [
// v-if 也作为一个 Block
// 现在,我们有了 Block Tree!
{ tag: 'div', dynamicChildren: []},
{ tag: 'section', dynamicChildren: []},
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
问题2:并不能彻底解决可能的边界情况,采用兜底方案,使用原有diff算法。
资料:vue3渲染playground (opens new window)
// 新的数据结构block tree,对应新的渲染函数
// disableTracking用于判断是否回退diff
function openBlock(disableTracking = false) {
blockStack.push((currentBlock = disableTracking ? null : []));
}
function createBlock(type, props, children, patchFlag, dynamicProps) {
const vnode = createVNode(type, props, children, patchFlag, dynamicProps, true);
// 构造 `Block Tree`
vnode.dynamicChildren = currentBlock || EMPTY_ARR;
closeBlock();
// 注意是需要条件的
if (someCondition === true) {
currentBlock.push(vnode);
}
return vnode;
}
// 不是所有的都可以是 Block 的
if (
isBlockTreeEnabled > 0 &&
// avoid a block node from tracking itself
!isBlockNode &&
// has current parent block
currentBlock &&
// presence of a patch flag indicates this node needs patching on updates.
// component nodes also should always be patched, because even if the
// component doesn't need to update, it needs to persist the instance on to
// the next vnode so that it can be properly unmounted later.
(patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
// the EVENTS flag is only for hydration and if it is the only flag, the
// vnode should not be considered dynamic due to handler caching.
patchFlag !== PatchFlags.HYDRATE_EVENTS
) {
currentBlock.push(vnode)
}
<div>
<h1>hello world!</h1>
</div>
const render = ()
=> (openBlock(), createBlock('div', null, [createVNode('h1', null, 'hello world!')]))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 2、缓存提升
动态节点提升
哪些不可被提升:动态props、ref、自定义指令等
// hoists,虚拟dom缓存提升,非动态,只会执行一次
let __h1__ = createVNode('h1', null, 'hello world!')
const render = ()
=> (openBlock(), createBlock('div', null, [__h1__]))
2
3
4
动态节点属性提升
提升局部属性,动态节点中不变的数据进行属性提升
<div>
<h1 class='foo'>{{ text }}</h1>
</div>
// props hoists
let __cls__ = { class: 'foo' }
const render = () => (openBlock(), createBlock('div', null, [
createVNode('p', __cls__, ctx.text, PatchFlags.TEXT)
]))
2
3
4
5
6
7
8
9
预先字符串化
将多个节点进行字符串拼接,统一append至节点中,减少dom操作
// 预先字符串化!!!
export function createStaticVNode(
content: string,
numberOfNodes: number
): VNode {
// A static vnode can contain multiple stringified elements, and the number
// of elements is necessary for hydration.
const vnode = createVNode(Static, null, content)
vnode.staticCount = numberOfNodes
return vnode
}
// 当然是满足一定量的情况,比如超过 20 个满足条件的节点,才能被预先字符串化
2
3
4
5
6
7
8
9
10
11
12
13
事件提升
将模版中的事件统一提升至变量中,避免每次编译时绑定事件都为新事件需要重新渲染
import React from 'react'
import A from './components/A'
function App() {
return (//每次编译都需render,每次onclick都为新函数,可对事件进行缓存避免重复渲染
<A onClick={() => handleSomeClick()} />
)
}
const handler = () => handleSomeClick()
(openBlock(), createBlock(A, { onClick: handler }))
2
3
4
5
6
7
8
9
10
11
代码库拆分
原编译库拆分成多个 compile-* 的文件夹,用于整体模块解耦合,例如react拆分为react和react-dom
DETAILS
面试题:vue3是如何变的更快?(底层、源码)
diff方法优化:动静分离处理,使用patchFlag进行动态属性标记,实现靶向更新
- vue2.0中虚拟dom是进行全量的对比
- vue3.0中新增了静态标记(patchFlag),再与上次的虚拟节点进行对比时,只对比带有patchFlag的节点,且在具体节点对比时可根据patchFlag的值得知当前节点要对比的具体内容,实现靶向更新
hositStatic静态提升
- vue2.0无论元素是否参与更新,每次都会重新创建
- Vue3.0对于不参与更新的元素,只会被创建一次,之后会在每次渲染时进行复用
cacheHandlers事件监听器缓存:默认情况下onclick等事件会视为动态绑定,所以每次都会追逐它的变化,但是因为是同一个函数,直接进行缓存复用,无需追踪变化。