概览
最近在学习vue3源码,源码中有一段藏在角落不起眼的代码,细品之后发现其技巧性很强。这不,他就是vue3巧用位运算,使用一个变量来表示虚拟节点及其children的身份。
场景
vue根据虚节点(以下简称:vnode)来渲染其页面,vnode有一个属性shapeFlag表示其身份。在vue3中虚拟节点有很多中种类,如:元素、函数组件、带状态的组件等。vnode同时有一个属性children来表示其孩子,孩子也有很多身份,如:文本节点,数组,插槽等。 vue是怎么通过一个type就能判断虚拟节点本身及其孩子的身份的了?
巧用位运算
使用左移(<<)来表示身份
vue里面有一个描述虚拟节点身份的文件,他的结构是ts的枚举,其中就使用到了左移运算符,具体如下
export const enum ShapeFlags {
// 元素
ELEMENT = 1,
// 函数组件
FUNCTIONAL_COMPONENT = 1 << 1,
// 带状态组件
STATEFUL_COMPONENT = 1 << 2,
// 描述儿子
// 文本儿子节点
TEXT_CHILDREN = 1 << 3,
// 数组节点
ARRAY_CHILDREN = 1 << 4,
// 插槽
SLOTS_CHILDREN = 1 << 5,
}
可以发现里面采用了位运算,我们用八位二进制数表示如下:
export const enum ShapeFlags {
// 元素
ELEMENT = 1 = 0000 0001,
// 函数组件
FUNCTIONAL_COMPONENT = 1 << 1 = 2 = 0000 0010,
// 带状态组件
STATEFUL_COMPONENT = 1 << 2 = 4 = 0000 0100,
// 描述儿子
// 文本儿子节点
TEXT_CHILDREN = 1 << 3 = 8 = 0000 1000,
// 数组节点
ARRAY_CHILDREN = 1 << 4 = 16 = 0001 0000,
// 插槽
SLOTS_CHILDREN = 1 << 5,
}
使用或(|)来计算节点身份
以下是一个真实dom:
<p>
我不想上班
</p>
这个真实dom用vnode描述,其结构可能如下:
const vnode = {
type: 'p',
shapeFlag: // ? 这里应该怎么表示其是一个元素,其孩子是一个文本节点
children: '我不想上班'
}
我们使用或进行以下运算, shapeFla = ELEMENT 或上 TEXT_CHILDREN
vnode.shapeFlag = ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN
// 1 | 8 = 0000 0001 | 0000 1000 = 0000 1001 = 9
最终shapeFlag = 0000 1001 = 9
接下来我们通过一系列的例子来说明,怎么判断身份。
使用与(&)来判断其身份
由上可得一个vnode,表示如下:
const vnode = {
type: 'p',
shapeFlag: 9, // 0000 1001
children: '我不想上班'
}
下面进行一系列的测试:
- 判断该节点是不是一个带状态的组件
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT)
0000 1001 & 0000 0100 = 0000 0000 = 0 由此可以得出,这是一个错误结论
- 判断该节点是不是一个元素节点
if (vnode.shapeFlag & ShapeFlags.ELEMENT)
0000 1001 & 0000 0001 = 0000 0001 = 1 由此可以得出,他是元素节点
- 判断该节点的孩子是不是数组
if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN)
0000 1001 & 0001 0000 = 0000 0000 = 0 由此得出,他的孩子不是数组
- 判断该节点的孩子是不是文本
if (vnode.shapeFlag & ShapeFlags.TEXT_CHILDREN)
0000 1001 & 0000 1000 = 0000 1000 = 8 由此得出,他的孩子是文本节点
进过测试我们发现,通过与运算,我们可以只使用一个变量,就能判断节点本身和其孩子的身份
总结
总结一下,差不多三步:
- 使用左移来定义身份类型
- 使用或来组合类型
- 使用与来判断类型
好的代码读起来赏心悦目简直就是远方的诗,现实却是在屎山沉沦,但没办法,生活要继续,眼前的苟且要解决!