『 深入 VUE 』响应

简单的响应

给定 a 和 b 两个参数,要求无论 a 如何变化,b 永远等于 a 的 10 倍,如何实现?

即如下效果:

1
2
3
4
5
6
let a = 3
let b = a * 10
console.log(b) // 30
a = 4
b = a * 10
console.log(b) // 40

在每次改变 a 的值之后手动更新 b 的值,这是山顶洞人的做法,我们想要的是 a 的值改变之后,自动执行 b 的改变函数。

我们可以维护一个 state 对象:

1
2
3
4
state = {
a: 10,
b: 100
}

然后每次 a 发生变化时执行:

1
2
3
onStateChanged(() => {
state.b = 10 * state.a
})

要实现这个很简单,我们可以每次改变 a 的值从原来的直接赋值变为调用一个函数:

1
setState({ a: 5})

setState 这个函数里做赋值操作,同时执行 onStateChanged 函数使 b 的值自动得到更新。

完整的实现如下:

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
let update
let state = {}
const onStateChanged = _update => {
update = _update
}

const setState = newState => {
state = newState
update()
}

onStateChanged(() => {
state.b = 10 * state.a
})

state = {
a: 10,
b: 100
}

setState({ a: 5})

console.log(state.b) // 50

setState({ a: 50})

console.log(state.b) // 500

严格来说应该把 stateupdateonStateChangedsetState 封装起来,当初始化值之后,只允许通过 setState 来改变变量的值。这其实已经有点像 react 了。

使用 Object.defineProperty

这里用到一个对象方法 Object.defineProperty,目的是将 data 对象里的所有属性通过这个方法来重新定义,重写属性的修改(setter)方法。

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
// 初始化 data 对象
const data = {
a: 10,
b: 100
}

// 将属性 a 的值临时存放
let value = data.a

// 重写 data.a 的方法
Object.defineProperty(data, 'a', {
get() {
return value
},
set(val) {
value = val
data.b = 10 * val // 这样一来每次对 a 重新赋值时便会自动更新 b 的值
}
})



console.log(data.b) // 100

data.a = 3

console.log(data.b) // 30

vue 的响应

使用 Object.defineProperty 方法已经和 vue 的实现有点接近了,vue 实例是将 data 对象里的所有属性通过这个方法来重新定义,实现属性被访问(getter)和修改(setter)时通知(Notify)变化(Watcher 调用函数)。

即实现如下功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const data = {
a: 0
}

// observe 函数将 data 的属性重写
observe(data)

// triggerFunction 接受一个函数作为参数,当 data 中属性值改变时自动调用 triggerFunction 里的函数
triggerFunction(() => {
console.log(data)
})

// 0

data.a++
// 1

具体的实现:

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
// 在全局定义一个 Watch 类
window.Watcher = class Watcher {
constructor () {
// 订阅所有 Watch 搜集到的属性
this.subscribers = new Set()
}

depend () {
if (activeUpdate) {
// 将属性自动更新方法添加到订阅列表中
this.subscribers.add(activeUpdate)
}
}

notify () {
// 当属性被重新赋值时触发 setter 函数通知 Watch 做出改变
this.subscribers.forEach(subscriber => subscriber())
}
}

let activeUpdate

function observe (obj) {
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
let watcher = new Watcher()
Object.defineProperty(obj, key, {
get () {
watcher.depend()
return internalValue
},
set (newValue) {
const isChanged = internalValue !== newValue
if (isChanged) {
internalValue = newValue
watcher.notify()
}
}
})
})
}

function triggerFunction (update) {
function wrappedUpdate () {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}

上面的 triggerFunction 只是简单地 console.loga 的值,其实可以进一步地实现 re-render 函数,使得页面的 html 元素也自动重新渲染,便是完整的 vue 响应了。

相关文章

深入响应式原理
Object.defineProperty