# Vue数据双向绑定与响应式
# Object.definProperty
# 描述符可拥有的键值
configurable | enumerable | value | writable | get | set | |
|---|---|---|---|---|---|---|
| 数据描述符 | 可以 | 可以 | 可以 | 可以 | 不可以 | 不可以 |
| 存取描述符 | 可以 | 可以 | 不可以 | 不可以 | 可以 | 可以 |
如果一个描述符不具有
value、writable、get和set中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有value或writable和get或set键,则会产生一个异常。
即一个描述符不可同时拥有value 或 writable 和 get 或 set 键(不可同时拥有数据描述符和存取描述符)
即一个描述符要么有value和writable属性,要么有get和set属性。
const data = {}
Object.defineProperty(data, 'name', {
configurable: false,
enumerable: false,
value: 'ls', // 值,默认为undefined
writable: true // 是否可写,默认false
})
data.name = 'zs'
console.log(data.name)
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
const data = {}
let temp = 'zs'
Object.defineProperty(data, 'name', {
configurable: false,
enumerable: false,
get() {
console.log('get value')
return temp
},
set(newVal) {
console.log('set newVal')
temp = newVal
}
})
data.name = 'ls'
console.log(data.name)
// set newVal
// get value
// ls
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
# 双向绑定MVVM
- 数据层(Model):应用的数据及业务逻辑
- 视图层(View):应用的展示效果,各类UI组件
- 业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来
# 理解ViewModel
它的主要职责就是:
- 数据变化后更新数据
- 视图变化后更新数据
第一点通过事件监听很容易实现
第二点需要解决几个问题:
- 当数据改变时,视图如何知道数据发生了改变
- 数据改变时,我们如何知道应该更新哪个视图的数据 这里我们通过订阅发布模式来解决这个问题
此外,它还有两个主要部分组成
- 监听器(Observer):对所有数据的属性进行监听
- 解析器(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
# 实现观察者Observer
因为definProperty只能监听某个对象的某个属性,所以我们借助递归来实现监听整个对象
/**
* dep数组用于通知
* subs: subs数组,存储订阅者
* notify(): 通知订阅者数据发生改变
* addSub(): 添加订阅者到数组
*/
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
// 通知每个订阅者更新
notify() {
this.subs.forEach((item) => {
item.update()
})
}
}
/**
* 订阅者:监听数据变化,将每个订阅者存入Dep数组中
*/
class Observer {
constructor(data) {
this.dep = new Dep()
this.observerAll(data)
}
// 监听一个值
observer(data, key, value) {
this.observerAll(value)
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
// 将订阅者存入数组
if (Dep.target) {
this.dep.addSub(Dep.target)
}
console.log('get ' + value)
return value
},
set(newValue) {
console.log('set ' + newValue)
value = newValue
// 数据变化,通知订阅者
this.dep.notify()
},
})
}
// 递归监听对象
observerAll(data) {
if (Object.prototype.toString.call(data) !== '[object Object]') {
return
}
Object.keys(data).forEach((key) => {
this.observer(data, key, data[key])
})
}
}
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
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
# 订阅者Watcher
const { Dep } = require('./Observer')
/**
* vm: 监听的对象
* exp: 监听的属性名
* cb: 数据改变时执行的回调函数
*/
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm
this.exp = exp
this.cb = cb
this.value = this.get()
}
update() {
this.run()
}
run() {
let oldValue = this.value
if (oldValue !== this.vm[this.exp]) {
this.value = this.vm[this.exp]
this.cb(this.value)
}
}
// 返回vm[exp]的值
get() {
Dep.target = this
let value = this.vm[this.exp]
Dep.target = null
return value
}
}
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
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
参考:
https://juejin.cn/post/6844904084114259981#heading-2
https://segmentfault.com/a/1190000016208088
← Vue与React的组件透传 虚拟dom →