Vue部分源码分析

VUE3 框架的三大模块

  1. compiler 编译系统
  2. runtime 渲染系统
  3. reactive 响应系统

Reactive 的实现

在 Vue2 中实现响应式是使用Object.defineProperty来实现数据劫持如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let number = 18
let person = {
name: '月晕',
}
Object.defineProperty(person,'age',{
value:18 // 设置属性的值
writable: true // 如果为 true,属性的值可以被修改;如果为 false,属性的值是只读的。
enumerable:true //如果为 true,属性可以通过对象的迭代方法(例如 for...in 循环)枚举;如果为 false,属性不可枚举。
configurable:true // 如果为 true,属性的特性可以被修改或者删除;如果为 false,属性的特性不可更改。
get(){
console.log('有人访问age属性了')
return number
},
set(value){
number = value
console.log('有人修改了age属性',value)
}
})

在 Vue3 中使用 Proxy 来实现数据劫持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let number = 18
let person = {
name: '月晕',
}
let proxy = new Proxy(person,{
get(target,key,reactive){
console.log('有人访问get属性了')
return Reflect.get(target,key,reactive)
},
set(target,key,value,reactive){
console.log('有人修改了age属性',value)
return Reflect.set(target,key,value,reactive)
}
})

Proxy 的优势

  1. Proxy 可以直接监听对象而非属性
  2. Proxy 用于创建一个对象的代理,从而实现基本操作的拦截和自定义劫持整个对象,并返回一个新对象。
  3. Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。
  4. Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue2.X 里,是通过递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象才是更好的选择。

Reflect

  • 不是一个函数对象,因此它是不可构造的。直接使用静态方式即可。
  • 反射
  • 作用:可以通过编程的方式操作对象
  • 用法和 Object 类似,但是 Object 具有局限性
  • 比如增加删除属性需要写 try catch,而 Reflect 不需要 直接 if else 判断即可
  • 比如在 object 的 key 只能是 String,而 Reflect 可以 value-value
  • Reflect 提供的是一整套反射能力 API,它们的调用方式,参数和返回值都是统一风格的,我们可以使用 Reflect 写出更优雅的反射代码。

Reactive 的实现

  • reactive.js
1
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
44
45
46
47
48
49
50
51
// 引入一些工具函数
import {isObject,hasChanged,isArray} from './utils'
// 引入track trigger 更新触发函数
import {track,trigger} from './effect'
// 建立proxyMap映射表 用于存储代理对象(代理对象相同时直接引用就行)
const proxyMap = new WeakMap()
export function reactive(target) {
// 1. 判断传入的target是否是对象
if (!isObject(target)) {
return target
}
// 2.判断是否是reactive包reactive
if(isReactive(target)) {
return target
}
if(proxyMap.has(target)) {
return proxyMap.get(target)
}
const proxy = new Proxy(target,{
// receiver 代理对象本身
get(target,key,receiver){
const res = Reflect.get(target,key,receiver)
// 如果是(reactive(reacitve))的情况(重复代理情况)
if(key==='__isReactive') return true
// 收集依赖
track(target,,key)
// 递归遍历深层次对象
return isObject(res) ? reactive(res) : res
},
set(target,key,value,receiver){
// 处理数组
const oldLength = target.length
const oldVlaue = target[key]
const res = Reflect.set(target,key,value,receiver)
if(hasChanged(oldVlaue,value)) {
// 触发更新
trigger(target,key)
if(isArray(target)&&hasChanged(oldLength,target.length)) {
trigger(target,'length')
}
}
}
})
proxyMap.set(target,proxy)
return proxy
}
// 多次代理
export function isReactive(target) {
// 强行转化成布尔值
return !!(target && target.__isReactive)
}
  • effect.js
1
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/**
* activeEffect 用于存储当前的effect函数
* 作用:用于在track中存储依赖收集
*/
let activeEffect
// effectStack 用于存储effect函数的栈防止丢失effect函数
const effectStack = []
export function effect(fn, options = {}) {
const effectFn = () => {
try {
activeEffect = effectFn
// 入栈
effectStack.push(effectFn)
return fn()
} finally {
// 出栈
effectStack.pop()
// 恢复activeEffect
activeEffect = effectStack[effectStack.length - 1]
}
}
// options.lazy 为true时不会立即执行effect函数(区别computed和watch和普通的响应)
if (!options.lazy) {
effectFn()
}
if (options.sheduler) {
effectFn.sheduler = options.sheduler
}
return effectFn
}
// 依赖收集
const targetMap = new WeakMap()
export function track(target, key) {
// 如果没有activeEffect 说明不是在effect中执行的则不需要收集依赖直接返回
if (!acctiveEffect) {
return
}
//depsMap Map对象 用于存储目标对象与其属性之间的依赖关系。它的作用是将目标对象映射到一个包含属性依赖的 Map,以便在需要时快速找到对应属性的依赖集合。
let depsMap = targetMap.get(target)
// 如果没有depsMap 说明是第一次收集依赖
if (!depsMap) {
// 初始化depsMap
targetMap.set(target, (depsMap = new Map()))
}
// dep是Set对象,它用于存储具体属性的依赖集合。每个属性都有一个对应的 dep 集合,其中存储了依赖于该属性的副作用函数(effects)。
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
dep.add(acctiveEffect)
}

export function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
const dep = depsMap.get(key)
if (!dep) {
return
}
dep.forEach((effectFn) => {
if (effectFn.sheduler) {
effectFn.sheduler()
} else {
effectFn()
}
})
}
  • utils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export function isObject(val) {
// 类型是null 类型是object
return typeof val === 'object' && val !== null
}

export function haschanged(oldValue, newValue) {
return (
oldValue !== newValue && !(Number.isNaN(oldValue) && Number.isNaN(newValue))
)
}

export function isArray(target) {
return Array.isArray(target)
}

export function isFunction(target) {
return typeof target === 'function'
}

Ref 实现

ref.js 如下

1
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
import { isObject, hasChanged } from './utils'
import { track, trigger } from './effect'
import { reactive } from './reactive'
export function ref(value) {
if (isRef(value)) return value
return new RefImpl(value)
}
export function isRef(value) {
return value.__isRef
}
class RefImpl {
constructor(value) {
this._value = convert(value)
this.__isRef = true
}
get value() {
track(this, 'value')
return this._value
}
set value(newValue) {
if (hasChanged(newValue, this._value)) {
this._value = convert(newValue)
tigger(this, 'value')
}
}
}

function convert(value) {
return isObject(value) ? reactive(value) : value
}

computed 实现
computed.js 如下

1
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
44
import { isFunction } from '../utils'
import { effect, track, trigger } from './effect'
export function computed(getterOrOptions) {
let getter, setter
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
setter = () => {
console.warn('computed value must be readonly')
}
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(getter, setter)
}

class ComputedRefImpl {
constructor(getter, setter) {
this._dirty = true
this._value = null
this._setter = setter
this.effect = effect(getter, {
lazy: true,
// 调度机制
scheduler: () => {
if (!this._dirty) {
this._dirty = true
trigger(this, 'value')
}
}
})
}
get value() {
if (this._dirty) {
this._value = this.effect()
this._dirty = false
}
track(this, 'value')
return this._value
}
set value(newValue) {
this._setter(newValue)
}
}

runtime 渲染系统

首先介绍一下什么是虚拟 DOM

虚拟 DOM:

  • 用 JS 对象来描述 DOM 节点
  • 种类有:Element、Text、Fragment、
  1. Element:
    对应普通元素,如 div、p、span 等,使用 doucment.createElement 创建,type 指定标签名,props 指定元素属性,children 指定子元素,可以为数组或者字符串,为字符串时代表只有一个文本子节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 类型定义
{
type: string,
props: Object,
children: string | VNode[]
}
// 例子
{
type: 'div',
props: {
id: 'app'
},
children: 'hello world'
}
  1. Text:
    对应文本节点,使用 document.createTextNode 创建,text 指定文本内容
1
2
3
4
5
{
type: symbol,
props: null,
text: string
}
  1. Fragment:
    对应 Fragment,不会渲染的节点,相当于 templete 或 react 的 Fragment,type 为 symbol,props 为 null,children 为数组表示子节点,最后渲染时会将子节点的所有子节点挂载到 Fragment 父节点上
1
2
3
4
5
{
type: symbol,
props: null,
children: VNode[]
}
  1. Components:
    Component 是组件,组件有自己特殊的一套渲染方法,但组件的最终产物,也是上面三种 VNode 的集合。组件的 type,就是组件定义的对象,props 即是外部传入组件的 props 数据,children 是组件的 slot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 类型定义
{
type: Object,
props: Object,
children: null
}
// 例子
{
type:{
template: `{{msg}} {{name}}`
props: ['name'],
setup(){
return {
msg: 'hello'
}
}
},
props: {
name: 'world'
},
}

ShaperFlags

  • 一组标记,用于快速识别 VNode 的类型和他的子节点类型
  • 使用位运算
1
2
3
4
5
6
7
8
9
10
11
// 例子
// 与运算,只有两个都为1时才为1
0 0 1 0 0 0 1 1
0 0 1 0 1 1 1 1
&
0 0 1 0 0 0 1 1
// 或运算,只要有一个为1就为1
0 0 1 0 0 0 1 1
0 0 1 0 1 1 1 1
|
0 0 1 0 1 1 1 1
1
2
3
4
5
6
7
8
9
const ShapeFlags = {
ELEMENT: 1, // 普通元素 00000001
TEXT: 1 << 1, // 文本节点 00000010
FRAGMENT: 1 << 2, // Fragment 00000100
COMPONENT: 1 << 3, // 组件 00001000
TEXT_CHILDREN: 1 << 4, // 子节点是文本 00010000
ARRAY_CHILDREN: 1 << 5, // 子节点是数组 00100000
CHILDREN: (1 << 4) | (1 << 5) // 子节点是文本或数组
}

采用二进制位运算<<|,使用时用&运算判断,如下:

1
2
3
4
5
6
7
8
9
if (flag & ShapeFlags.ELEMENT) {
// 是普通元素
}
let flag = 33
flag & ShapeFlags.ELMENT // 1 true
flag & ShapeFlags.TEXT // 0 false
flag & ShapeFlags.FRAGMENT // 0 false
flag & ShapeFlags.ARRAY_CHILDREN // true
flag & ShapeFlags.CHILDREN // true

生成可以用
let flag = ShapeFlags.ELEMENT | ShapeFlags.ARRAY_CHILDREN

VNode 的初步形成

1
2
3
{
type, props, children, shapeFlag
}

vnode.js

1
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
44
45
46
47
48
49
50
import { isArray, isNumber, isString } from '../utils'
// ShapeFlags: 二进制位运算
export const ShapeFlags = {
ELEMENT: 1,
TEXT: 1 << 1,
FRAGMENT: 1 << 2,
COMPONENT: 1 << 3,
TEXT_CHILDREN: 1 << 4,
ARRAY_CHILDREN: 1 << 5,
CHILDREN: (1 << 4) | (1 << 5)
}

// Text:类型Symbol
export const Text = Symbol('Text')
// Fragment:类型SymbolS
export const Fragment = Symbol('Fragment')

/**
* vnode有四种类型:dom元素,纯文本,Fragment,组件
* @param {string | Object | Text | Fragment} type
* @param {Object | null} props
* @param {String | Array | null | number} children
* @returns {VNode}
*/

export function h(type, props, children) {
let shapeFlag = 0
if (isString(type)) {
shapeFlag = ShapeFlags.ELEMENT
} else if (type === Text) {
shapeFlag = ShapeFlags.TEXT
} else if (type === Fragment) {
shapeFlag = ShapeFlags.FRAGMENT
} else {
shapeFlag = ShapeFlags.COMPONENT
}
if (isString(children) || isNumber(children)) {
// a|=b ==>
shapeFlag |= ShapeFlags.TEXT_CHILDREN
children = children.toString()
} else if (isArray(children)) {
shapeFlag |= ShapeFlags.ARRAY_CHILDREN
}
return {
type,
props,
children,
shapeFlag
}
}

render.js

1
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import { ShapeFlags } from './vnode'
export funtion render (vnode,container) {
mount(vnode,container)
}

funtion mount (vnode,container) {
const { shapeFlag } = vnode
// 判断不同类型的vnode
if(shapeFlag & ShapeFlags.ELEMENT){
mountElement(vnode,container)
}else if(shapeFlag & ShapeFlags.TEXT){
mountText(vnode,container)
}else if(shapeFlag & ShapeFlags.FRAGMENT){
mountFragment(vnode,container)
}else if(shapeFlag & ShapeFlags.COMPONENT){
mountComponent(vnode,container)
}
}

function mountElement (vnode,container) {
const { type,props } = vnode
const el = document.createElement(type)
// 挂载属性
mountProps(props,el)
// 挂载子节点
mountChildren(vnode,el)
// 挂载到容器
container.appendChild(el)
}

function mountText (vnode,container) {
const textNode = doucment.createTextNode(vnode.children)
container.appendChild(textNode)
}

function mountFragment (vnode,container) {
mountChildren(vnode,container)
}

function mountComponent (vnode,container) {

}

// 区别 attributes 和 直接的dom api
const domPropsRE = /\[A-Z]|^(?:value|checked|selected|muted)$/

function mountProps (props,el) {
for (const key in props) {
const value = props[key]
switch (key) {
case 'class':
el.className = value
break;
case 'style':
for(const styleName in value){
el.style[styleName] = value[styleName]
}
break;
default:
if(/^on[^a-z]/.test(key)){
const eventName = key.slice(2).toLowerCase()
el.addEventListener(eventName,value )
} else if(domPropsRE.test(key)){
// {"checked":''}
if(value === '' && typeof el[key] === 'boolean'){
value = true
}
el[key] = value
} else {
if(value == null || value === false){
el.removeAttribute(key)
} else{
el.setAttribute(key,value)
}
}
break;
}
}
}

function mountChildren (vnode,container) {
const {shapeFlag,children} = vnode
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
mountText(vnode,container)
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// 遍历子节点 递归
children.forEach(child => mount(child,container))
}
}

实现广义的 diff 算法 patch

render.js

1
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
import { ShapeFlags } from './vnode'
// 比较vnode的Props差异函数
import { patchProps } from './patchProp'
// 渲染render函数
export function render(vnode, container) {
// 初次渲染
// 上一次的vnode存储
const prevVNode = container._vnode
// first: 判断n2是否存在(即newVNode)
if (!vnode) {
// 如果n1存在,则卸载
if (prevNode) {
// 卸载
unmount(prevVNode)
container._vnode = null
}
} else {
// n2存在 进行patch差异化比较
patch(prevVNode, vnode, container)
}
container._vnode = vnode
}

/**
* @param {VNode | null} n1:旧的vnode
* @param {VNode} n2:新的vnode
* @param {Element} container:容器
* @param {Element | null} anchor:锚点 (插入位置)
*/
function patch(n1, n2, container, anchor) {
if (n1 && !isSameVNode(n1, n2)) {
// n1存在 且 n1和n2不是同一个vnode
// n1被卸载后,n2将会创建,因此anchor至关重要。需要将它设置为n1的下一个兄弟节点
anchor = (n1.anchor || n1.el).nextSibling
umount(n1)
n1 = null
}
const { shapeFlag } = n2
if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(n1, n2, container, anchor)
} else if (shapeFlag & ShapeFlags.TEXT) {
processText(n1, n2, container, anchor)
} else if (shapeFlag & ShapeFlags.FRAGMENT) {
processFragment(n1, n2, container, anchor)
} else {
processElement(n1, n2, container, anchor)
}
}

/**
* @param {VNode} vnode: vnode DOM节点
* @param {Element} container:容器
* @param {Element | null} anchor:锚点 (插入位置)
* @apprehen 递归创建子节点
*/
function mountElement(vnode, container, anchor) {
const { type, props, shapeFlag, children } = vnode
const el = doucment.createElement(type)
// 挂载属性
if (props) {
patchProps(null, props, el)
}
// 挂载子节点
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
el.textContent = children // 子结点是文本
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children, el) // 子结点是数组 遍历子节点挂载
}
}

/**
* @param {VNode} vnode:vnode DOM节点
* @param {Element} container:容器
* @param {Element | null} anchor:锚点 (插入位置)
* @apprehen
*/
function mountText(vnode, container, anchor) {
const el = document.createTextNode(vnode.children)
vnode.el = el
container.insertBefore(el, anchor)
}

/**
* @param {Array<VNode>} children:children DOM节点数组 分别挂载
* @param {Element} container:容器
* @param {Element | null} anchor:锚点 (插入位置)
* @apprehen 挂载子节点(数组类型)
*/
function mountChildren(children, container, anchor) {
children.forEach((child) => {
patch(null, child, container, anchor)
})
}

/**
* @param {VNode} vnode:vnode DOM节点
* @apprehen 卸载vnode
*/
function unmount(vnode) {
const { shapeFlag, el } = vnode
if (shapeFlag & ShapeFlags.COMPONENT) {
unmountComponent(vnode)
} else if (shapeFlag & ShapeFlags.FRAGMENT) {
unmountFragment(vnode)
} else {
el.parentNode.removeChild(el)
}
}

/**
* @param {VNode} vnode:vnode DOM节点
* @apprehen 卸载组件vnode
*/
function unmountComponent(vnode) {
// TODO
}

/**
* @param {VNode} vnode:vnode DOM节点
*/
function unmountFragment(vnode) {
const { el: cur, anchor: end } = vnode
const { parentNode } = cur
while (cur !== end) {
const next = cur.nextSibling
parentNode.removeChild(cur)
cur = next
}
parentNode.removeChild(end)
}

/**
* @param {Array<VNode>} Children: children DOM节点数组 分别卸载
* @apprehen 对比vnode是否相同
*/
function unmountChildren(Children) {
Children.forEach((child) => {
unmount(child)
})
}

/**
* @param {VNode} n1:旧的vnode
* @param {VNode} n2:新的vnode
* @param {Element} container:容器
* @param {Element | null} anchor:锚点 (插入位置)
* @apprehen 对比元素Element并进行更新
*/
function processElement(n1, n2, container, anchor) {
if (n1 == null) {
// 直接挂载n2
mountElement(n2, container, anchor)
} else {
patchElement(n1, n2)
}
}

/**
* @param {VNode} n1:旧的vnode
* @param {VNode} n2:新的vnode
* @param {Element} container:容器
* @param {Element | null} anchor:锚点 (插入位置)
* @apprehen 对比Fragment并进行更新
*/
function processFragment(n1, n2, container, anchor) {
const fragmentStartAnchor = (n2.el = n1 ? n1.el : document.createTextNode(''))
const fragmentEndAnchor = (n2.anchor = n1
? n1.anchor
: document.createTextNode(''))
if (n1 == null) {
container.insertBefore(fragmentStartAnchor, anchor)
container.insertBefore(fragmentEndAnchor, anchor)
mountChildren(n2.children, container, fragmentEndAnchor)
} else {
patchChildren(n1, n2, container, fragmentEndAnchor)
}
}

/**
* @param {VNode} n1:旧的vnode
* @param {VNode} n2:新的vnode
* @param {Element} container:容器
* @param {Element | null} anchor:锚点 (插入位置)
* @apprehen 组件进行更新
*/
function processComponent(n1, n2, container, anchor) {
// TODO
}

/**
* @param {VNode} n1:旧的vnode
* @param {VNode} n2:新的vnode
* @param {Element} container:容器
* @param {Element | null} anchor:锚点 (插入位置)
* @apprehen 对比文本并进行更新
*/
function processText(n1, n2, container, anchor) {
if (n1 == null) {
mountText(n2, container, anchor)
} else {
n2.el = n1.el
n2.el.textContent = n2.children
}
}

/**
* @param {VNode} n1:旧的vnode
* @param {VNode} n2:新的vnode
* @apprehen 对比vnode是否相同(比较复用)
*/
function patchElement(n1, n2) {
n2.el = n1.el
patchProps(n1.props, n2.props, n2.el)
patchChildren(n1, n2, n2.el)
}

/**
* @param {VNode} n1:旧的vnode
* @param {VNode} n2:新的vnode
* @param {Element} container:容器
* @param {Element | null} anchor:锚点 (插入位置)
* @apprehen 对比vnode是否相同(比较复用)判断n1 n2 的子结点类型
*/
function patchChildren(n1, n2, container, anchor) {
const { shapeFlag: prevShapeFlag, children: c1 } = n1
const { shapeFlag, children: c2 } = n2
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
// n2 是TEXT_CHILDREN 类型
if (c2 !== c1) {
container.textContent = c2
}
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
unmountChildren(c1)
}
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// n2 是ARRAY_CHILDREN 类型
if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
// n1 是TEXT_CHILDREN 类型
container.textContent = ''
mountChildren(c2, container, anchor)
} else if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// n1 是ARRAY_CHILDREN 类型
patchArrayChildren(c1, c2, container, anchor)
} else {
// n1 是EMPTY_CHILDREN 类型 null
mountChildren(c2, container, anchor)
}
} else {
// n2 是EMPTY_CHILDREN 类型 null
if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
// n1 是TEXT_CHILDREN 类型
container.textContent = ''
} else if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// n1 是ARRAY_CHILDREN 类型
unmountChildren(c1)
}
}
}

/**
* @param {VNode} n1:旧的vnode
* @param {VNode} n2:新的vnode
* @param {Element} container:容器
* @param {Element | null} anchor:锚点 (插入位置)
*/
function patchArrayChildren(c1, c2, container, anchor) {
const oldLength = c1.length
const newLength = c2.length
const commonLength = Math.min(oldLength, newLength)
for (let i = 0; i < commonLength; i++) {
patch(c1[i], c2[i], container, anchor)
}
if (oldLength > newLength) {
unmountChildren(c1.slice(commonLength))
} else {
mountChildren(c2.slice(commonLength), container, anchor)
}
}

function isSameVNode(prevVNode, vnode) {
return prevVNode.type === vnode.type
}

vnode.js

1
2
3
4
5
6
7
8
9
10
11
//在return中新添加属性
return {
types,
props,
children,
shapeFlag,
el: null,
anchor: null,
key: props && (props.key != null ? props.key : null),
component: null //专门用于存储组件的实例
}

vue3 中 diff 算法
首先是重新完成 render.js

1
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import { ShapeFlags } from './vnode'
import { patchProps } from './patchProps'
import { mountComponent } from './component'
export function render(vnode, container) {
const prevVNode = container._vnode
// first:判断n2是否存在
if (!vnode) {
if (prevVNode) {
unmount(prevVNode)
}
} else {
// n2存在
patch(prevVNode, vnode, container)
}
// 存prevVNode
container._vnode = vnode
}
// ....
// 简单的diff比较
function patchUnkeyedChildren(c1, c2, container, anchor) {
const oldLength = c1.length
const newLength = c2.length
const commonLenght = Math.min(oldLength, newLength)
for (let i = 0; i < commonlength; i++) {
patch(c1[i], c2[i], container)
}
if (oldLength > newLength) {
unmountChildren(c1.slice(commonLength))
} else {
mountChildren(c2.slice(commonLength), container, anchor)
}
}
// react中的diff算法原理
function reactPathchKeyedChildren(c1, c2, container, anchor) {
const map = new Map()
c1.forEach((perv, j) => {
map.set(perv.key, { perv, j })
})
let maxNewIndexSoFar = 0
for (let i = 0; i < c2.length; i++) {
const next = c2[i]
const curAnchor = i - 1 < 0 ? c1[0].el : c2[i - 1].el.nextSibling
if (map.has(next.key)) {
const { prev, j } = map.get(next.key)
patch(prev, next, container, anchor)
if (j < maxNewIndexSoFar) {
container.insertBefore(next.el, curAnchor)
} else {
maxNewIndexSoFar = j
}
map.delete(next.key)
} else {
patch(null, next, container, curAnchor)
}
}
map.forEach(({ perv }) => {
unmount(perv)
})
}

// Vue3中的diff算法 双端 + 最长子序列算法
function patchKeyedChildren(c1, c2, container, anchor) {
let i = 0
let e1 = c1.length - 1
let e2 = c2.length - 1
// 1.从左向右依次对比
while (i <= e1 && i <= e2 && c1[i].key === c2[i].key) {
patch(c1[i], c2[i], container, anchor)
i++
}
// 2.从右向左一次对比
while (i <= e1 && i <= e2 && c1[e1].key === c2[e2].key) {
patch(c1[e1], c2[e1], container, anchor)
e1--
e2--
}
if (i > e1) {
// 3. 经过1,2 直接将旧结点比对完,则剩下的新结点直接mount
for (let j = i; j <= e2; j++) {
const nextPos = e2 + 1
const curAnchor = (c2[nextPos] && c2[nextPos].el) || anchor
path(null, c2[j], container, curAnchor)
}
} else if (i > e2) {
// 3. 经过1,2 直接将新结点比对完,则剩下的旧结点直接unmount
for (let j = 1; j <= e1; j++) {
unmount(c1[j])
}
} else {
// 4. 若不满足3 采用传统的diff算法 但是不真的移动和添加,只做标记和删除
const map = new Map()
c1.forEach((perv, j) => {
map.set(perv.key, { perv, j })
})
let maxNewIndexSoFar = 0
let move = false
const source = new Array(e2 - i + 1).fill(-1)
const toMounted = []
for (let k = 0; k < c2.length; k++) {
const next = c2[k]
if (map.has(next.key)) {
const { prev, j } = map.get(next.key)
patch(prev, next, container, anchor)
if (j < maxNewIndexSoFar) {
move = true
} else {
maxNewIndexSoFar = j
}
source[k] = j
map.delete(next.key)
} else {
toMounted.push(k + i)
}
}
map.forEach(({ perv }) => unmount(perv))
if (move) {
// 5.需要移动,则采用新的最长上升子序列算法
const seq = getSequence(source)
let j = seq.length - 1
for (let k = source.length - 1; k >= 0; k--) {
if (k === seq[j]) {
// 不用移动
j--
} else {
const pos = k + i
const nextPos = pos + 1
const curAnchor = (c2[nextPos] && c2[nextPos].el) || anchor
if (source[k] === -1) {
// mount
patch(null, c2[pos], container, curAnchor)
} else {
// 移动
container.insertBefore(c2[pos].el, curAnchor)
}
}
}
} else if (toMounted.length) {
// 6.不需要移动,但还有未添加的元素
for (let k = toMounted.length - 1; k >= 0; k--) {
const pos = toMounted[k]
const nextPos = pos + 1
const curAnchor = (c2[nextPos] && c2[nextPos].el) || anchor
patch(null, c2[pos], container, curAnchor)
}
}
}
}

// 求最长子序列
function getSequence(nums) {
const result = []
const position = []
for (let i = 0; i < nums.length; i++) {
if (nums[i] === -1) {
continue
}
// result[result.length - 1]可能为undefined,此时nums[i] > undefined为false
if (nums[i] > result[result.length - 1]) {
result.push(nums[i])
position.push(result.length - 1)
} else {
let l = 0,
r = result.length - 1
while (l <= r) {
const mid = ~~((l + r) / 2)
if (nums[i] > result[mid]) {
l = mid + 1
} else if (nums[i] < result[mid]) {
r = mid - 1
} else {
l = mid
break
}
}
result[l] = nums[i]
position.push(l)
}
}
let cur = result.length - 1
// 这里复用了result,它本身已经没用了
for (let i = position.length - 1; i >= 0 && cur >= 0; i--) {
if (position[i] === cur) {
result[cur--] = i
}
}
return result
}

组件的实现方法

从开发者的视角:组件分为状态组件和函数组件
vue3 中的状态组件和函数组件类似,下面只讨论状态组件的实现

React 的组件示例(class 组件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Counter extends React.Component {
state = {
count: 0
}
add = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.add}>add</button>
</div>
)
}
}

Vue3 的组件示例(optional) (渲染函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
createApp({
data() {
return {
count: 0
}
},
methods: {
add() {
this.count++
}
},
render(cxt) {
return [
h('div', {}, [
h('p', {}, ctx.count),
h('button', { onClick: ctx.add }, 'add')
])
]
}
}).mount('#app')

Vue3 的组件示例(composition) (渲染函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
createApp({
setup() {
const count = ref(0)
const add = () => {
count.value++
}
return {
count,
add
}
},
render(cxt) {
return [
h('div', {}, [
h('p', {}, ctx.count),
h('button', { onClick: ctx.add }, 'add')
])
]
}
}).mount('#app')

可以看出 从实现的角度上来说,组件都有以下几个共同点:

  • 都有 instance (实例) 以承载内部的状态,方法等
  • 都有一个 render 函数
  • 都通过 render 函数产出VNode
  • 都有一套更新的策略,以重新执行 render 函数
  • 在此基础上附加各种能力,如生命周期,通信机制,slot,provide,inject 等

component.js

1
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import { reactive } from '../reactive/reactive'
import { effect } from '../reactive/effect'
import { patch } from './render'
import { normalizeVNode } from './vnode'
import { queueJob } from './scheduler'
function updateProps(instance, vnode) {
const { type: Component, props: vnodeProps } = vnode
const props = (instance.props = {})
const attrs = (instance.attrs = {})
for (const key in vnodeProps) {
if (Component.props?.includes(key)) {
props[key] = vnodeProps[key]
} else {
attrs[key] = vnodeProps[key]
}
}
// toThink: props源码是shallowReactive,确实需要吗?
// 需要。否则子组件修改props不会触发更新
instance.props = reactive(instance.props)
}
export function mountComponent(vnode, container, anchor) {
const { type: Component } = vnode
const instance = (vnode.component = {
props: {},
attrs: {},
setupState: null,
subTree: null,
ctx: null,
update: null,
isMounted: false,
next: null
})
// setupComponent
updateProps(instance, vnode)
// 源码:instance.setupState = proxyRefs(setupResult)
instance.setupState = Component.setup?.(instance.props, {
attrs: instance.attrs,
slot: null,
emit: null
})
instance.ctx = {
...instance.props,
...instance.setupState
}
instance.update = effect(
() => {
if (!instance.isMounted) {
// mount
const subTree = (instance.subTree = normalizeVNode(
Component.render(instance.ctx)
))
fullThrough(instance, subTree)
patch(null, subTree, container, anchor)
instance.isMounted = true
vnode.el = subTree.el
} else {
if (instance.next) {
// 被动更新
vnode = instance.next
instance.next = null
updateProps(instance, vnode)
instance.ctx = {
...instance.props,
...instance.setupState
}
}
const prev = instance.subTree
const subTree = (instance.subTree = normalizeVNode(
Component.render(instance.ctx)
))
fullThrough(instance, subTree)
patch(prev, subTree, container, anchor)
vnode.el = subTree.el
}
},
{ scheduler: queueJob }
)
}

function fullThrough(instance, subTree) {
if (Object.keys(instance.attrs).length) {
subTree.props = {
...subTree.props,
...instance.attrs
}
}
}

scheduler.js (调度机制) nextTick 原理

1
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
const queue = []
let isFlushing = false
const resolvedPromise = Promise.resolve()
let currentFlushPromise = null

export function nextTick(fn) {
return fn
? (currentFlushPromise || resolvedPromise).then(fn)
: currentFlushPromise || resolvedPromise
}

export function queueJob(job) {
// 如果队列中没有这个job,或者队列为空,就把job推入队列
if (!queue.includes(job) || !queue.length) {
queue.push(job)
queueFlush()
}
}

function queueFlush() {
if (!isFlushing) {
isFlushing = true
currentFlushPromise = resolvedPromise.then(flushJobs)
}
}

function flushJobs() {
try {
for (let i = 0; i < queue.length; i++) {
queue[i]()
}
} finally {
isFlushing = false
queue.length = 0
currentFlushPromise = null
}
}

编译步骤

模板代码 –> parse –> AST –> transform –> AST+codegenNode –> codegen –> 渲染函数代码

parse: 原始的模板代码就是一段字符串,通过解析parse 转换为原始AST抽象语法树
transform: 对AST进行转换,转换为codegenNode, codegenNodeAST到生成渲染函数代码的中间步骤,它由解析原始AST的语义而得来

1
2
<div v-if="ok" />
<div id="ok" />

没有什么区别,都是一个元素,带有一个不同的属性而已。然而v-if是带有特殊语义的,不能像一般的纯元素节点一样采用同样的代码生成方式。transform的作用就在于此,一方面解析原始AST的语义,另一方面要为生成代码做准备. transfrom 是整个 vue compiler 模块中最复杂的部分

codegen: 即是 code generate 遍历 codegenNode 递归地生成最终的渲染函数的代码

parse 实现

认识 AST

1
<div id="foo" v-if="ok">hello {{name}}</div>

AST Node 的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const NodeTypes = {
ROOT: 'ROOT', // 根节点
ELEMENT: 'ELEMENT', // 元素节点
TEXT: 'TEXT', // 文本节点
SIMPLE_EXPRESSION: 'SIMPLE_EXPRESSION', // 简单表达式
INTERPOLATION: 'INTERPOLATION', // 插值表达式
ATTRIBUTE: 'ATTRIBUTE', // 属性节点
DIRECTIVE: 'DIRECTIVE' // 指令节点
}

const ElementTypes = {
ELEMENT: 'ELEMENT', // 普通元素
COMPONENT: 'COMPONENT' // 组件
}
  1. 根节点
1
2
3
4
{
type: NodeTypes.ROOT,
children: TemplateChildNode[], // 子节点
}
  1. 纯文本节点
1
2
type: NodeTypes.TEXT,
content: string
  1. 表达式节点
1
2
3
4
5
6
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: string, // 表达式内容
// 静态即content就是一段字符串,动态的content指的是一个变量,或一段js表达式
isStatic: boolean, // 是否是静态的
}
  1. 插值节点
1
2
3
4
5
6
7
8
{
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: string, // 表达式内容
isStatic: boolean, // 是否是静态的
}
}
  1. 元素节点
1
2
3
4
5
6
7
8
9
{
type: NodeTypes.ELEMENT,
tag: string, // 标签名
tagType: ElementTypes, // 元素类型(组件还是原生元素)
props: Array<AttributeNode | DirectiveNode>, // 属性
directives: DirectiveNode[], // 指令数组
isSelfClosing: boolean, // 是否自闭合
children: TemplateChildNode[], // 子节点
}
  1. 属性节点
1
2
3
4
5
6
7
8
{
type: NodeTypes.ATTRIBUTE,
name: string, // 属性名
value: undefined | {
type: NodeTypes.TEXT | , // 属性值类型
content: string, // 属性值
} // 纯文本节点
}
  1. 指令节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
type: NodeTypes.DIRECTIVE,
name: string, // 指令名
exp: undefined | {
type: NodeTypes.SIMPLE_EXPRESSION, // 表达式类型
content: string, // 表达式内容
isStatic: boolean, // 是否是静态的
}, // 表达式节点
arg: undefined | {
type: NodeTypes.SIMPLE_EXPRESSION, // 表达式类型
content: string, // 表达式内容
isStatic: boolean, // 是否是静态的
}, // 表达式节点
}

1
2
3
4
<div v-bind:class="myClass"></div>
<!-- name:bind arg:class exp:myClass -->
<div @click="handleClick"></div>
<!-- name:on arg:click exp:handleClick -->

最终展示结果
<div id="foo" v-if="ok">hello {{name}}</div>

1
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
44
45
46
{
"type": "ROOT",
"children": [
{
type: "ELEMENT",
tag: "div",
tagType: "ELEMENT",
props:[
{
type: "ATTRIBUTE",
name: "id",
value: {
type: "TEXT",
content: "foo"
}
}
],
directives: [
{
type: "DIRECTIVE",
name: "if",
exp: {
type: "SIMPLE_EXPRESSION",
content: "ok",
isStatic: false
}
}
],
isSelfClosing: false,
children: [
{
type: "TEXT",
content: "hello "
},
{
type: "INTERPOLATION",
content: {
type: "SIMPLE_EXPRESSION",
content: "name",
isStatic: false
}
}
]
}
]
}

ast.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export const NodeTypes = {
ROOT: 'ROOT', // 根节点
ELEMENT: 'ELEMENT', // 元素节点
TEXT: 'TEXT', // 文本节点
SIMPLE_EXPRESSION: 'SIMPLE_EXPRESSION', // 简单表达式
INTERPOLATION: 'INTERPOLATION', // 插值表达式
ATTRIBUTE: 'ATTRIBUTE', // 属性节点
DIRECTIVE: 'DIRECTIVE' // 指令节点
}

export const ElementTypes = {
ELEMENT: 'ELEMENT', // 普通元素
COMPONENT: 'COMPONENT' // 组件
}

export function createRoot(children) {
return {
type: NodeTypes.ROOT,
children: children
}
}

parse.js

1
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
import { NodeTypes, ElementTypes, createRoot } from './ast'

export function parse(content) {
const context = createParserContext(content)
const children = parseChildren(context)
return createRoot(children)
}
function createParserContext(content) {
return {
options: {
delimiters: ['{{', '}}'] // 插值表达式的分隔符
},
source: content
}
}

function parseChildren(context) {
const s = context.source
if (s.startsWith(context.options.delimiters[0])) {
// parseInterpolation
} else if (s[0] === '<') {
// parseElement
} else {
// parseText
}
}

function advanceBy(context, numberOfCharacters) {
context.source = context.source.slice(numberOfCharacters)
}

function advanceSpaces(context) {
const match = /^[\t\r\n\f ]+/.exec(context.source)
if (match) {
advanceBy(context, match[0].length)
}
}