介绍mvvm和mvc

MVVM 是 Model-View-ViewModel 的缩写

Model: 代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。我们可以把Model称为数据层,因为它仅仅关注数据本身,不关心任何行为.

View: 用户操作界面(UI组件)。当ViewModel对Model进行更新的时候,会通过数据绑定更新到View

ViewModel:业务逻辑层,View需要什么数据,ViewModel要提供这个数据;View有某些操作,ViewModel就要响应这些操作,所以可以说它是Model for View. ViewModel把Model和View关联起来

MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

总结:MVVM模式简化了界面与业务的依赖,解决了数据频繁更新。MVVM 在使用当中,利用双向绑定技术,使得 Model 变化时,ViewModel 会自动更新,而 ViewModel 变化时,View 也会自动变化

实现一个简易mvvm

强烈推荐大佬的 gitHub地址(剖析Vue实现原理 - 如何实现双向绑定mvvm)[https://github.com/DMQ/mvvm]
最终效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="app">
<h1>{{msg}}</h1>
<span>{{count}}</span>
<span>{{person}}</span>
</div>
<script src="vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: '模拟vue',
count: 22,
person: 'cpp'
}
})
vm.person = '陈大鹏'
</script>

vue.js

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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// Observer: 监听器 能够对数据所有属性监听,如有变动拿到最新值并通知Dep
// Dep: 订阅器,又称消息订阅器,主要是收集订阅者watcher(或者称收集依赖),主要属性有 subs、addSub/notify,数据变动触发notify,再调用订阅者的update方法
// Watcher: 订阅者,又称观察者,订阅Observer中的属性值变化的信息,连接监听器和compiler的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数
// Compiler: 指令解析器 解析Vue模板指令 将模板中的变量替换成数据,绑定相应的更新函数
class Vue {
constructor(options = {}) {
// 保存选项中的数据
this.$options = options || {}
this.$data = options.data || {}
this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el;
// 将data中的成员转挂载到this上
// this._proxyData(this.$data)
// 调用Observer 监听数据变化
new Observer(this.$data)
// 调用compiler 解析vue模板指令和差值表达式
new Compiler(this)
}
_proxyData(data) {
// 遍历
Object.keys(data).forEach(key => {
Object.defineProperty(this, key, {
get() {
return data[key]
},
set(val) {
if (val === data[key]) {
return
}
data[key] = val
}
})
})
}
}
// 数据观测
class Observer {
constructor(data) {
this.walk(data)
}
walk(data) {
if (!data || typeof data !== 'object') {
return
}
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key]);
this.walk(data[key])
})
}
defineReactive(obj, key, val) {
let dep = new Dep()
let that = this;
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// 由于需要在闭包内添加watcher,所以通过Dep定义一个全局target属性,暂存watcher, 添加完移除
console.log('Dep.target 当前的wtacher 才能收集依赖')
Dep.target && dep.addSub(Dep.target)
return val
},
set: function reactiveSetter(newValue) {
if (val === newValue) {
return
}
that.walk(newValue);
dep.notify()
val = newValue
}
})
}
}
// Compile 模板解析
class Compiler {
constructor(vm) {
this.el = vm.$el;
this.vm = vm;
this.compile(this.el)
}
// 编译模板 处理文本节点 3和元素节点 1
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)
}
if (node && node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
// 编译元素节点 nodeType:1
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
// 处理textUpdater/modelUpdater
this.update(node, key, attrName)
}
})
}
update(node, key, attrName) {
this[attrName + 'Updater'] && this[attrName + 'Updater'](node, this.vm.$data[key], key)
}
// 处理 v-text 指令
textUpdater(node, value, key) {
node.textContent = value
new Watcher(this.vm.$data, key, (newValue) => {
node.textContent = newValue
})
}
// v-model
modelUpdater (node, value, key) {
node.value = value;
new Watcher(this.vm.$data, key, (newValue) => {
node.value = newValue
})
// 双向绑定
node.addEventListener('input', () => {
this.vm.$data[key] = node.value
})
}
// 编译文本节点 3 插值
compileText(node) {
let reg = /\{\{(.+?)\}\}/;
let val = node.textContent;
if (reg.test(val)) {
let key = RegExp.$1.trim()
node.textContent = val.replace(reg, this.vm.$data[key]);
new Watcher(this.vm.$data, key, (val) => {
node.textContent = val
})
}
}

// 判断元素属性是否是指令 ??
isDirective(attrName) {
return attrName.startsWith('v-')
}
// 是否是文本节点
isTextNode(node) {
return node.nodeType === 3
}
// 是否是元素节点
isElementNode(node) {
return node.nodeType === 1
}
}
// 订阅器 管理watcher
class Dep {
constructor() {
// 存放所有的订阅者
this.subs = []
}
// 添加订阅者 watcher
addSub(sub) {
console.log('addSub 开始收集依赖', sub)
this.subs.push(sub)
}
// 通知订阅者执行订阅者里的update方法
notify() {
this.subs.forEach((sub) => {
sub.update()
})
}
}
// 订阅者 Watcher
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
// 回调函数负责更新视图
this.cb = cb;
// 此处为了触发属性的getter,从而在dep添加自己,结合Observer更易理解
this.value = this.get()
}
get() {
// 将当前订阅者指向自己
Dep.target = this
// 触发getter,添加自己到属性订阅器中
var value = this.vm[this.key]
// 添加完毕,重置
Dep.target = null
return value
}
update() {
var value = this.get()
var oldValue = this.value
if (value !== oldValue) {
this.value = value
this.cb(value)
}
}
}

参考