Vue源码解析

(更新中)

数据响应式原理

将数据变为响应式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 让 data 变为响应式
// let val 临时变量也是可以的, 但是不美观, 可以封装到函数 defineReactive 中 , 也方便复用
export default function defineReactive(data, key, val) {
// 没传 val 的话 就使用原有的值
if (arguments.length === 2) {
val = data[key]
}
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
// getter
get() {
console.log('访问 obj 的' + key + '属性')
return val
},
// setter
set(newValue) {
console.log('改变 obj 的' + key + '属性', newValue)
if (val === newValue) return
val = newValue
}
})
}

递归侦测对象全部属性

若对象层级深,深层对象并不是响应式的,需要递归对象的全部属性

创建 Observer 类 将一个 object 转换为每个层级的属性都是响应式的

入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// \src\index.js
import observe from './observe.js'

let obj = {
a: 1,
b: {
c: 2
}
}

/* 递归入口 */
observe(obj)

obj.b.c = 5
console.log(obj.b.c)

observe 辅助函数

作用: 1.判断递归出口 2.避免循环引用反复创建 Observer 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// \src\observe.js

import Observer from "./Observer"

/* 创建 observe 函数 -- 作用: 1.判断递归出口 2.避免循环引用反复创建 Observer 实例 */
export default function (value) {
// 递归的出口
if (typeof value !== 'object') return
let ob
// 防止循环引用
if (typeof value.__ob__ !== 'undefined') {
ob = value.__ob__
} else {
ob = new Observer(value)
}
return ob
}

Observer 类

给 value __ob__ 属性, 遍历所有属性并调用 defineReactive 使它们变为响应式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// \src\Observer.js

import { def } from './utils.js'
import defineReactive from './defineReactive'

/* Observer 类, 给 value __ob__ 属性, 遍历所有属性并调用 defineReactive 使它们变为响应式 */
export default class Observer {
constructor(value) {
// this 为 new 的实例
def(value, '__ob__', this, false) // 目前 __ob__ 的意义好像只有在 observe 中判断循环引用
console.log('Observer 构造器', value)
this.walk(value)
}

// 遍历所有键, 让其响应式
walk(value) {
for (let k in value) {
defineReactive(value, k)
}
}
}

defineReactive 函数

让 data 变为响应式, 对每个 val 调用 observe, 开启递归

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
// \src\defineReactive.js

import observe from './observe.js'

/* 让 data 变为响应式, 对每个 val 调用 observe, 开启递归 */
// let val 临时变量也是可以的, 但是不美观, 可以封装到函数 defineReactive 中 , 也方便复用
export default function defineReactive(data, key, val) {
// 没传 val 的话 就使用原有的值
console.log('我是 defineReactive', data, key)
if (arguments.length === 2) {
val = data[key]
}

// 子元素要进行 observe, 至此形成递归. 这个递归是函数循环调用,不是自己调自己
let childOb = observe(val)

Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
// getter
get() {
console.log('访问' + key + '属性')
return val
},
// setter
set(newValue) {
console.log('改变' + key + '属性', newValue)
if (val === newValue) return
val = newValue
// 当设置了新值, 新值也要 observe
childOb = observe(newValue)
}
})
}

辅助函数

重写下 Object.defineProperty, 将 enumerable 变为参数

1
2
3
4
5
6
7
8
9
10
11
// \src\utils.js

/* 重写下 Object.defineProperty, 将 enumerable 变为参数 */
export const def = function(obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true,
configurable: true
})
}

整个流程

1
2
3
4
5
6
7
8
9
10
11
12
13
执行observe(obj)
├── new Observer(obj),并执行this.walk()遍历obj的属性,执行defineReactive()
├── defineReactive(obj, a)
├── 执行observe(obj.a) 发现obj.a不是对象,直接返回
├── 执行defineReactive(obj, a) 的剩余代码(定义obj的a属性的响应式)
├── defineReactive(obj, b)
├── 执行observe(obj.b) 发现obj.b是对象
├── 执行 new Observer(obj.b),遍历obj.b的属性,执行defineReactive()
├── 执行defineReactive(obj.b, c)
├── 执行observe(obj.b.c) 发现obj.b.c不是对象,直接返回
├── 执行defineReactive(obj.b, c)的剩余代码(定义obj.b的c属性的响应式)
├── 执行defineReactive(obj, b)的剩余代码(定义obj的b属性的响应式)
代码执行结束

递归

不是自己调用自己的递归,还是三个函数相互调用的循环

当传入 observe 的值不是对象时跳出递归

数组的响应式处理

为什么需要处理数组的响应式

正因为我们可以通过Array原型上的方法来改变数组的内容,所以 object 那种通过getter/setter的实现方式就行不通了

ES6 之前没有提供可以拦截原型方法的能力,我们可以用自定义的方法去覆盖原生的原型方法。

Vue 是通过改写数组的七个方法(可以改变数组自身内容的方法)来实现对数组的响应式处理

这些方法分别是:push、pop、shift、unshift、splice、sort、reverse

处理思路

这七个方法都是定义在 Array.prototype上,要保留方法的功能,同时增加数据劫持的代码

思路就是 以 Array.prototype为原型,创建一个新对象 arrayMethods
然后在新对象arrayMethods上定义(改写)这些方法
定义 数组 的原型指向 arrayMethods

这就相当于用一个拦截器覆盖 Array.prototype,每当使用Array原型上的方法操作数组时,其实执行的是拦截器中提供的方法。在拦截器中使用原生Array的原型方法去操作数组。

重写数组方法

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
// array.js
import { def } from './utils.js'

const arrayPrototype = Array.prototype

// 以 arrayPrototype 为原型创建 arrayMethods 对象
export const arrayMethods = Object.create(arrayPrototype)

const methodsNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]

methodsNeedChange.forEach(methodName => {
// 备份原来的方法
const original = arrayPrototype[methodName]

// 定义新的方法
def(arrayMethods, methodName, function() {
// 恢复原来的功能
const result = original.apply(this, arguments)

// 把这个数组身上的 __ob__ 取出来,
const ob = this.__ob__

// 有三种方法 push unshift splice 能插入新项, 要将新项变为 observe的
// inserted 用来存要插入的项
let inserted = []

// 根据方法名来判断要插入的项是什么并存入 inserted
switch(methodName) {
case 'push':
case 'unshift':
inserted = arguments
break
case 'splice':
inserted = [...arguments].slice(2)
break
}

// 判断有没有要插入的新项, 让新项也变为响应的
if (inserted.length) {
ob.observeArray(inserted)
}

console.log('啦啦啦')
return result
}, false)
})

observeArray

如果是数组的话走 observeArray 来遍历所有键;否则走 walk

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 { def } from './utils.js'
import defineReactive from './defineReactive.js'
import { arrayMethods } from './array.js'
import observe from './observe.js'

/* Observer 类, 给 value __ob__ 属性, 遍历所有属性并调用 defineReactive 使它们变为响应式 */
export default class Observer {
constructor(value) {
// this 为 new 的实例
def(value, '__ob__', this, false) // 目前 __ob__ 的意义好像只有在 observe 中判断循环引用

// 检查是数组还是对象
if (Array.isArray(value)) {
// 如果是数组, 将这个数组的原型指向 arrayMethods (arrayMethods的原型上的方法是改写过的)
Object.setPrototypeOf(value, arrayMethods)
// 让这个数组变得 observe
this.observeArray(value)
} else{
this.walk(value)
}
}

// 遍历所有键, 让其响应式
walk(value) {
for (let k in value) {
defineReactive(value, k)
}
}

// 数组的特殊遍历
observeArray(arr) {
for (let i = 0, l = arr.length; i < l; i++) {
// 逐项 observe
observe(arr[i])
}
}
}

依赖

Dep 类

依赖收集,用来管理依赖

每个 Observer 的实例成员中都有一个 Dep 的实例

Watcher 类

中介,数据发生

参考

https://www.bilibili.com/video/BV1G54y1s7xV/?spm_id_from=333.788.recommend_more_video.0&vd_source=1616b746cefe2e7615c229563ba38eb4

https://juejin.cn/post/6932659815424458760

https://blog.csdn.net/weixin_44972008/article/details/115922118


Vue源码解析
http://example.com/2022/07/10/Vue源码解析/
Author
John Doe
Posted on
July 10, 2022
Licensed under