# Vue 响应式核心原理
# 高级抽象
onStateChanged(() => {
view = render(state)
})
2
3
将整个响应式系统抽象为一个onStateChanged
方法,view自动根据state的变化而变化
那么如何实现onStateChanged
呢?
先看下手动触发更新是怎么写的
let update;
const onStateChanged = _update => {
update = _update;
}
const setState = newState => {
state = newState;
update();
}
2
3
4
5
6
7
8
9
10
每次更新时传入newState
: {a: 1}
, 手动触发setState
onStateChanged(() => {
view = render(state)
})
setState({ a: 1})
2
3
4
5
用过react的同学会发现,这就是整个react的工作核心。
那么,如果我不想手动setState
,只想改变状态state.a = 2
,然后view自动更新,如何做到autorun
呢?
我们都知道NG是采用脏检测的方式(这里不展开叙述)
而vue是通过ES5的object.defineProperty()
方法把数据对象变成响应式的可观察对象(observable),把所有属性转为getters
和setters
,这些 getter/setter
对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化,这段话暂时不理解没关系,我们后面再回来理解。
autorun(() => {
console.log(state.count)
})
2
3
这里是整个vue追踪变化的核心
下面我们尝试来自己实现一个最简单的响应式系统,主要分三块
- 将数据对象转成
getter/setter
- 实现依赖追踪
dependency-tracking
- 实现
mini-observer
# 将数据对象转成getter/setter
takes an Object as the argument
converts the Object's properties in-place into getter/setters using
Object.definePropertyThe converted object should retain original behavior, but at the same time
log all the get/set operations.
functionconvert (obj) {
// 取出obj的每个key进行循环将整个obj所有属性都变成getter/setter
Object.keys(obj).forEach(key => {
// 这里其实是个闭包,需要取得初始值,不然初始化可能会拿到underfined
let internalValue = obj[key];
Object.defineProperty(obj, key, {
get() {
console.log(`getting key "${key}": ${internalValue}`);
return internalValue;
},
set(newValue) {
console.log(`setting key "${key}" to: ${newValue}`);
internalValue = newValue;
}
})
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
实现效果如下
constobj= { foo: 123 }
convert(obj)
obj.foo // 'getting key "foo": 123'
obj.foo = 234 // 'setting key "foo" to 234'
obj.foo // 'getting key "foo": 234'
2
3
4
5
6
# 实现依赖追踪dependency-tracking
- Create a
Dep
class with two methods:depend
andnotify
. - Create an
autorun
function that takes an updater function. - Inside the updater function, you can explicitly depend on an instance of
Dep
by callingdep.depend()
- Later, you can trigger the updater function to run again by calling
dep.notify()
.
先看我们要实现的效果
constdep = new Dep()
autorun(() => {
dep.depend()
console.log('updated')
})
// should log: "updated"
dep.notify()
// should log: "updated"
2
3
4
5
6
7
8
9
10
Dep
是一个class
, 它有两个方法: depend
和notify
,顾名思义,一个是收集依赖,一个通知更新
无论何时调用dep.notify()
, 传给autorun
的方法应该再次 自动执行
// 订阅者Dep 主要作用是存放watcher观察者对象// 这里为了简化概念,没有引入watcher,直接用变量替代// 下面重点讲到的activeUpdate 类似于Dep.target = this;class Dep {
constructor() {
// Set类数组,成员唯一不重复
this.subscribes = new Set();
}
// 依赖收集,其实就是收集watcher
depend() {
if (activeUpdate) {
this.subscribes.add(activeUpdate)
}
}
// 通知所有watcher对象更新视图
notify() {
this.subscribes.forEach(sub => sub());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
接下来看下autorun
的实现
let activeUpdate = null
function autorun (update) {
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate;
update();
activeUpdate = null;
}
wrappedUpdate()
}
2
3
4
5
6
7
8
9
10
这里需要好好理解一下
activeUpdate
是一个autorun
之外的变量
每当autorun
执行,activeUpdate = wrappedUpdate
代表的是autorun
内正在执行的整个模块,如下图
在依赖收集
depend() {
// 将整个`autorun`内正在执行的整个模块,也就是watcher存放到订阅列表中
if (activeUpdate) {
this.subscribes.add(activeUpdate)
}
}
2
3
4
5
6
# mini-observer
结合上面两个模块
class Dep {
constructor () {
this.subscribers = new Set()
}
depend () {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}
notify () {
this.subscribers.forEach(sub => sub())
}
}
function observe (obj) {
// 遍历对象所有属性,并全部转为getter/setters
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
// 每个属性都有一个订阅实例
const dep = new Dep()
Object.defineProperty(obj, key, {
// getter 负责依赖收集
get () {
dep.depend()
return internalValue
},
// setter 负责通知更新
set (newVal) {
const changed = internalValue !== newVal
internalValue = newVal
// 触发计算,视图更新
if (changed) {
dep.notify()
}
}
})
})
return obj
}
let activeUpdate = null
function autorun (update) {
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
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
可以测试一下如下代码
conststate = {
count: 0
}
observe(state)
autorun(() => {
console.log(state.count)
})
// "count is: 0"
state.count++
// "count is: 1"
2
3
4
5
6
7
8
9
10
11
12
13