实现 MVVM (四) - MVVM 单向绑定实现

结合前面的 Object.defineProperty 用法、发布订阅和观察者模式,可以实现一个 MVVM 的单向绑定。

回顾 MVVM

MVVM 是一种用于把数据和 UI 分离的设计模式。Model 表示应用程序使用的数据,View 是与用户进行交互的桥梁。ViewModel 充当数据转换器,将 Model 信息转换为 View 的信息,将命令从 View 传递到 Model


MVVM 单向绑定 Demo

JSbin


代码实现

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>MVVM 单向绑定</title>
</head>
<body>

<div id="app" >
<h1>{{name}} {{number}}</h1>
</div>

<script>
// 观察数据
function observe(data) {
if(!data || typeof data !== 'object') return
for(var key in data) {
let val = data[key]
let subject = new Subject() //遍历属性的过程中,对于每一个属性 new Subject()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
console.log(`get ${key}: ${val}`)

/*** 如果 currentObserver 出现,将观察者订阅到该主题 ***/
if(currentObserver){
console.log('has currentObserver')
currentObserver.subscribeTo(subject)
}

return val
},
set: function(newVal) {
val = newVal
console.log('start notify...')
//通知订阅者 执行 notify()
subject.notify()
}
})
if(typeof val === 'object'){
observe(val)
}
}
}

let id = 0
let currentObserver = null

class Subject {
constructor() {
this.id = id++
this.observers = []
}
addObserver(observer) {
this.observers.push(observer)
}
removeObserver(observer) {
var index = this.observers.indexOf(observer)
if(index > -1){
this.observers.splice(index, 1)
}
}
notify() {
this.observers.forEach(observer => {
observer.update()
})
}
}

// 观察者
class Observer{
constructor(vm, key, callback) {
this.subjects = {} // 订阅主题
this.vm = vm // mvvm 对象
this.key = key // data 的属性
this.callback = callback //callback
this.value = this.getValue()
}
// 更新值
update(){
let oldVal = this.value
let value = this.getValue()
// 彻底更新时,才会做改变
if(value !== oldVal) {
this.value = value
this.callback.bind(this.vm)(value, oldVal)
}
}
subscribeTo(subject) {
if(!this.subjects[subject.id]){
console.log('subscribeTo.. ', subject)
subject.addObserver(this)
this.subjects[subject.id] = subject
}
}
/*** 观察者变为全局 currentObserver ***/
getValue(){
currentObserver = this
let value = this.vm.$data[this.key] //相当于new Observer 之后会调用 observe 中的 get,然后执行其中的 if(currentObserver)
currentObserver = null
return value
}
}



class mvvm {
constructor(opts) {
this.init(opts) //初始化
observe(this.$data) //监听
this.compile() //解析模板
}
init(opts){
this.$el = document.querySelector(opts.el) //获取当前元素 el: 下的id
this.$data = opts.data
this.observers = []
}
// 解析 el 模板
compile(){
this.traverse(this.$el)
}
// 递归遍历情况 DOM nodeType 属性
traverse(node){
if(node.nodeType === 1){
node.childNodes.forEach(childNode=>{
this.traverse(childNode)
})
}else if(node.nodeType === 3){ //文本
this.renderText(node) // 渲染
}
}
// 渲染
renderText(node){
let reg = /{{(.+?)}}/g //正则取双大括号里面的内容
let match
while(match = reg.exec(node.nodeValue)){
let raw = match[0] // 原始
let key = match[1].trim() // 插值表达式内的值
node.nodeValue = node.nodeValue.replace(raw, this.$data[key]) // 替换大括号插值表达式里的内容为 data里面的数据

// Observer 方法 创建观察者
new Observer(this, key, function(val, oldVal){
node.nodeValue = node.nodeValue.replace(oldVal, val)
})
}
}

}

let vm = new mvvm({
el: '#app',
data: {
name: 'hello world',
number: 0
}
})

setInterval(function(){
vm.$data.number++
}, 1000)


</script>
</body>
</html>


单向绑定总结

实现单向数据绑定,只需要做两件事。

  • 观察 data 数据:通过 observe()
  • 解析模板:通过 class mvvm 构造函数中的 compile()

当数据发生改变的时候,就通过观察者 和 发布/订阅 来进行改变。

本文结束  感谢您的阅读
0%