vue响应式原理模拟
- 数据驱动
- 响应式原理
- 发布订阅模式和观察者模式
数据驱动
- 数据响应式、双向绑定、数据驱动
- 数据响应式
- 数据模型仅仅是普通的js对象,而当我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率
- 双向绑定
- 数据改变,视图改变;视图改变、数据也随之改变
- 我们可以使用v-model在表带元素上创建双向绑定数据
- 数据驱动是Vue最独特的特性之一
- 开发过程中仅需要关注数据本身,不需要关心数据如何渲染到视图
前置知识
- 两大模式
发布/订阅模式
- 由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在
class EventEmitter {
constructor() {
this.subs = Object.create(null) // 不需要原型链
}
// v注册事件
$on(eventType, handler) {
// 如果有值直接返回,如果没有值返回空数组
this.subs[eventType] = this.subs[eventType] || []
this.subs[eventType].push(handler)
}
// 触发事件
$emit(eventType) {
if (this.subs[eventType]) {
this.subs[eventType].forEach((handler) => {
handler()
})
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
观察者模式
- 由具体目标调用,比如当事件触发,dep就会去调用观察者方法,所以观察者模式的订阅者和发布者之间存在依赖关系
// 发布者-目标
class Dep {
constructor() {
// 记录所有的订阅者
this.subs = []
}
// 添加订阅者
addSub(sub) {
if (subs && sub.update) {
this.subs.push(sub)
}
}
// 发布通知
notify() {
this.subs.forEach((sub) => {
sub.update()
})
}
}
// 订阅者-观察者
class Watcher {
update() {
console.log('update')
}
}
// test
let dep = new Dep()
let watcher = new Watcher()
dep.addSub(watcher)
dep.notify()
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
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
整体结构
实现
- 功能
- 负责接收初始化的参数
- 吧data中的属性注入到vue实例中,转换成getter/setter
- 负责调用observer监听data中的所有属性变化
- 负责调用compiler解析指令和差值表达式
class Vue {
constructor(options) {
// 1. 通过属性保存选项的数据
this.$options = options || {}
this.$data = options.data || {}
this.$el =
typeof options.el === 'string'
? document.querySelector(options.el)
: options.el
// 2. 把data中的成员转换成getter/setter。 注入到vue实例中
this._proxyData(this.$data)
// 3. 调用observer对象,监听数据的变化
new Observer(this.$data)
// 4. 调用compiler对象,解析指令和差值表达式
new Compiler(this)
}
_proxyData(data) {
// 遍历data中的所有属性
Object.keys(data).forEach((key) => {
// 把data的属性注入到vue实例中
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return data[key]
},
set(newValue) {
if (newValue === data[key]) return
data[key] = newValue
},
})
})
}
}
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
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
observer.js
- 功能
- 负责把data选项中的属性转换成响应式数据
- data中的某个属性也是对象,把该属性转换成响应式数据
- 数据变化发送通知
class Observer {
constructor(data) {
this.walk(data)
}
// 遍历data中的所有属性
walk(data) {
// 1.判断data是否是对象
if (!data || typeof data !== 'object') return
// 2. 遍历data对象的所有属性
Object.keys(data).forEach((key) => {
this.defineReactive(data, key, data[key])
})
}
defineReactive(obj, key, val) {
let that = this
// 负责收集依赖,并发送通知
let dep = new Dep()
// 如果val是对象,吧val内部的属性转换成响应式数据
this.walk(val) // 递归调用
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
Dep.target && dep.addSub(Dep.target)
return val
},
set(newValue) {
if (newValue === val) return
val = newValue
that.walk(newValue)
// 发送通知
dep.notify()
},
})
}
}
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
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
compiler
- 功能
- 负责编译模板,解析指令和差值表达式
- 负责页面的首次渲染
- 当数据变化后重新渲染视图
class Compiler {
constructor(vm) {
this.el = vm.$el
this.vm = vm
this.compile(this.el)
}
// 编译模板,处理文本节点和元素节点
compile(el) {
let childNodes = el.childNodes
Array.from(childNodes).forEach((node) => {
// 处理文本节点
if (this.isTextNode(node)) {
this.compileText(node)
} else if (this.isElementNode(node)) {
// 处理元素节点
this.compileElement(node)
}
// 判断node节点,是否有子节点,如果有子节点,要递归调用compile
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
// 编译元素节点,处理指令
compileElement(node) {
// 遍历所有的属性节点
Array.from(node.attributes).forEach((attr) => {
// 判断是否是指令
let attrName = attr.name
if (this.isDirective(attrName)) {
// v-text ---> text
attrName = attrName.substr(2)
let key = attr.value
this.update(node, key, attrName)
}
})
}
update(node, key, attrName) {
let updateFn = this[attrName + 'Update']
updateFn && updateFn.call(this, node, this.vm[key], key)
}
// 处理v-text指令
textUpdate(node, value, key) {
node.textContent = value
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue
})
}
// v-model
modelUpdate(node, value, key) {
node.value = value
new Watcher(this.vm, key, (newValue) => {
node.value = newValue
})
// 双向绑定
node.addEventListener('input', () => {
this.vm[key] = node.value
})
}
// 编译文本节点,处理差值表达式
compileText(node) {
// {{ msg }}
let reg = /\{\{(.+?)\}\}/
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
// 创建watcher对象,当数据改变更新视图
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue
})
}
}
// 判断元素属性是否是指令
isDirective(attrName) {
return attrName.startsWith('v-')
}
// 判断节点是否是文本节点
isTextNode(node) {
return node.nodeType == 3
}
// 判断节点是否是元素节点
isElementNode(node) {
return node.nodeType == 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
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
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
Dep
- 功能
- 依赖收集、添加观察者
- 通知所有观察者
class Dep {
constructor() {
// 存储所有的观察者
this.subs = []
}
// 添加观察者
addSub(sub) {
if (sub && sub.update) {
this.subs.push(sub)
}
}
// 发送通知
notify() {
this.subs.forEach((sub) => {
sub.update()
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
watcher
- 功能
- 当数据变化触发依赖,dep通知所有的watcher实例更新试图
- 自身实例化的时候往dep对象中添加自己
class Watcher {
constructor(vm, key, cb) {
this.vm = vm
// data中的属性名称
this.key = key
// 回调函数负责更新试图
this.cb = cb
// 把watcher对象记录到Dep类的静态属性target
Dep.target = this
// 触发get方法,在get方法中调用addsubs
this.oldValue = vm[key]
Dep.target = null
}
// 当数据发生变化的时候更新试图
update() {
let newValue = this.vm[this.key]
if (this.oldValue === newValue) return
this.cb(newValue)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
整体流程
- 一个是第一次执行的时候,通过
new vue
将data
中的数据通过响应式放到vue实例下(为了实现this.xxx
的调用),然后调用observer
将data
中的所有数据递归橙响应式数据,在该get
和set
中放好收集依赖和发送通知的功能,最后调用compiler
解析指令 - 在使用
this.xxx
触发set
的时候会触发observer
中的dep.notify
,这个notify
会触发watcher
中的update
方法,执行在compiler
中写好的回调