Vue3 源码中的位运算
前言
位运算操作 在几乎所有编程语言中都被支持,位运算符可以将操作数转化成二进制串,然后在二进制的基础上执行运算,然后返回标准的数值。
由于位运算符属于低级运算操作,所以运算速度也是最快的,并且可以代替最基础的加减乘除运算,因此经常出现在一些条件有限制的场合中,例如算法题,最经典的就是不使用 + 和 - 计算两个整数之和。
但位运算操作也有很明显的缺点,就是不直观,在业务代码中使用位运算更是不值得推崇的做法。
霍春阳大佬的《Vue.js 设计与实现》中,在第一篇的第一章里面就有提到,Vue 3 在框架设计里到处都体现了权衡的艺术
,指的是 Vue3 在设计上有大量对于性能和可维护性之间选择的权衡,所以 Vue3 对于位运算的妙用,是非常值得学习的地方。
渲染器和 VNode
VNode 俗称虚拟 DOM,是一种用 JavaScript 对象描述 DOM 结构的一种方式,而渲染器就是 Vue 用于将 VNode 转译成真实 DOM 的模块。
Vue 提供了一个h
函数,用户可以利用这个h
函数来手动编写render
,h
接受三个参数:
- type:描述元素的类型
- props:描述元素拥有的属性
- children:子节点
最后,返回一个 VNode 虚拟 DOM 结点。
为了方便理解,可以用一个简化版的createVNode
函数来说明(实际上h
函数只是 Vue 为用户提供的简化调用方式,h
函数内部调用的方法正是createVNode
):
|
|
举个例子,如果需要使用h()
函数来创建以下这段 HTML 内容:
|
|
那么应该这样写:
|
|
看起来似乎非常完美😎,结构清晰层次分明。
不过仔细想一想,如果当前渲染的节点的 children 不是普通 HTML 元素,而是一个组件
:
|
|
这时候,Vue 应该做的就是渲染出Component
组件内的所有元素,而不是一个 HTML 标签,这种情况应该怎样用 VNode 来表示呢?
添加 shapeFlag
本篇博客的重点并不是源码分析,所以就不卖关子了,Vue 在渲染内容时,会在patch
阶段判断 VNode 类型,根据 VNode 的类型去走不同的渲染逻辑。
而判断类型的依据就是本次的主要学习对象shapeFlag
,shapeFlags
是一个枚举对象,它最特殊的点是利用了二进制来表示 VNode 的各个属性:
|
|
对应的值如下表:
ShapeFlag | 对应节点类型 | 位移 | 二进制位图 |
---|---|---|---|
ELEMENT | HTML | 1 | 0000000001 |
FUNCTIONAL_COMPONENT | 函数式组件 | 1 « 1 | 0000000010 |
STATEFUL_COMPONENT | 普通组件 | 1 « 2 | 0000000100 |
TEXT_CHILDREN | 子节点是纯文本 | 1 « 3 | 0000001000 |
ARRAY_CHILDREN | 子节点是数组 | 1 « 4 | 0000010000 |
SLOTS_CHILDREN | 子节点是插槽 | 1 « 5 | 0000100000 |
TELEPORT | 传送门 | 1 « 6 | 0001000000 |
SUSPENSE | SUSPENSE | 1 « 7 | 0010000000 |
COMPONENT_SHOULD_KEEP_ALIVE | 将要被 keep alive 的组件 | 1 « 8 | 0100000000 |
COMPONENT_KEPT_ALIVE | 已经被 keep alive 的组件 | 1 « 9 | 1000000000 |
为 VNode 添加状态
定义了枚举类型shapeFlag
,就可以对之前的createVNode
进行一些改造,创建 VNode 时将它标记为某种节点,这是为了对patch
过程进行优化。
例如,判断createVNode
函数的第一个参数的类型,如果是字符串类型,就可以将当前节点定义为ELEMENT
节点:
|
|
然后,如果当前节点拥有子节点的话,则可以同时标记子节点的类型。
也就是说这时候需要让一个节点同时拥有两种状态,例如:当前节点是ELEMENT并且子节点是数组,要实现这一点,只要使用位运算的或
运算符即可。
或
运算 a | b
,对比每一个比特位,当同一位上的数有一个为1,结果就为1,否则结果为0。
于是,可以通过判断子节点的类型,添加一个 shapeFlag 标记:
|
|
如果当前 VNode 的 shapeFlag 已经被标记为 ELEMENT,则二进制位图表示为:0000000001,
如果当前 VNode 的子节点是数组,则二进制位图表示为:0000010000,
将两个数值进行|
运算后,便可以得出新的 shapeFlag 的值为:0000010001
判断 VNode 的 shapeFlag 状态
有了 shapeFlag 之后,渲染器在patch
的时候就可以根据与
运算,判断 VNode 类型,再决定渲染逻辑就可以了。
与
运算 a & b
,对比每一个比特位,当同一位上的数都为1,结果才为1,否则结果为0。
|
|
处理 element 时,还需要判断子组件的 shapeFlag:
|
|
接上面的逻辑,假设 VNode 的 shapeFlag 被标记为:0000010001,
用 VNode 的 shapeFlag 标记去与 ShapeFlags.ELEMENT
的值 0000000001 进行与
运算,可以得到一个非0的值,非0的值用于if
判断,属于true
值,所以判断有效。
总结
位运算由于在实际开发中并不会经常用到,所以在学习 Vue3 源码时看到 shapeFlag 觉得还是比较有意思的。
但实际上 Vue3 对于 VNode 的操作和定义要比上面所描述的复杂得多,由于 VNode 的设计涉及到最关键的性能部分,可以说每一行代码都是经过仔细优化的,很难用一篇博客去详细展开这些细节,所以这次就仅仅是对 shapeFlag 这个点进行总结和学习,也已经感觉到获益良多。
(完)