简单实现一个mvvm

什么是MVVM

MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。

上代码

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
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<h>姓名:</h>
<p>{{name}}</p>
<h>姓名:</h>
<p>{{name}}</p>
<h>年龄:</h>
<p>{{age}}</p>
</div>
<script>
let target = null

class Mvvm {
constructor(options) {
this.options = options
const root = document.querySelector(options.el)
this.observer(options.data)
// 初始化dom树 主要是填充绑定的值和触发各个值的getter
this.compile(root)
}

observer(data) {
Object.keys(data).forEach(key => {
const dep = new Dep()
data['_' + key] = data[key]
Object.defineProperty(data, key, {
set(newVal) {
data['_' + key] = newVal
dep.update(newVal)
},
get() {
target && dep.addSub(target)
return data['_' + key]
},
})
})
}

compile(root) {
Array.prototype.forEach.call(root.childNodes, child => {
// 判断该节点是不是最底层节点 是就执行 不是就递归继续找
if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) {
// 拿到RegExp的全局只读属性
const key = RegExp.$1.trim()
child.innerHTML = this.options.data[key]
// 将该节点赋給target
target = child
// 触发这个key的get方法 将target以闭包的形式传到dep实例
this.options.data[key]
target = null
} else {
this.compile(child)
}
})
}
}

class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
update(val) {
this.subs.forEach(sub => {
sub.innerHTML = val
})
}
}

const vm = new Mvvm({
el: '#app',
data: {
name: '暂无',
age: 20,
},
})
setTimeout(function () {
vm.options.data.name = 'demo'
vm.options.data.age = 30
}, 2000)
</script>
</body>
</html>