forked from Hi-Sen/Vue_MVVM
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmvvm.html
207 lines (198 loc) · 5.48 KB
/
mvvm.html
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
200
201
202
203
204
205
206
207
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>模拟Vue,实现一个双向绑定</title>
</head>
<body>
<div id='app'>
<p>{{msg}}</p>
<br/>
<input v-m='test' type="text">
<p>{{test}}</p>
</div>
</body>
<script>
/*
第三步
1,实现一个 watcher 观察者/订阅者
订阅者原型上挂在两个方法 分别是
update 渲染视图
2,定义一个消息订阅器
很简单,维护一个数组,用来收集订阅者
消息订阅器原型挂载两个方法 分别是
addSub 添加一个订阅者
notify 数据变动 通知 这个订阅者的 update 方法
*/
function Watcher(vm, node, name, nodeType) {
Dep.target = this;
this.vm = vm;
this.node = node;
this.name = name;
this.nodeType = nodeType;
this.update();
console.log(Dep.target)
Dep.target = null;
}
Watcher.prototype = {
update: function () {
/*
this.node 指向当前修改的 dom 元素
this.vm 指向当前 dom 的实例对象
根据 nodeType 类型 赋值渲染页面
*/
if (this.nodeType === 'text') {
this.node.nodeValue = this.vm[this.name]
}
if (this.nodeType === 'input') {
this.node.value = this.vm[this.name]
}
}
}
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function (sub) {
this.subs.push(sub);
},
notify: function () {
this.subs.forEach(function (sub) {
sub.update();
});
}
}
/*
第二步
实现一个数据监听
1,获取当前实例对象的 data 属性 key
observer(当前实例对象 data ,当前实例对象)
2,使用 Object.defineProperty 方法 实现监听
*/
function observe(data, vm) {
Object.keys(data).forEach(function (key) {
defineReactive(vm, key, data[key]);
});
}
function defineReactive(vm, key, val) {
/*
Object.defineProperty
obj
要在其上定义属性的对象。
prop
要定义或修改的属性的名称。
descriptor
将被定义或修改的属性描述符。 描述符有很多,就包括我们要市用 set , get 方法
*/
var dep = new Dep();
Object.defineProperty(vm, key, {
get: function () {
/*
if (Dep.target) dep.addSub(Dep.target);
看到这段代码不要差异,生成每一个 dom节点,都会走 get 方法
这里为每一个节点 添加一个订阅者 到主题对象 Dep
*/
if (Dep.target) dep.addSub(Dep.target);
console.log(val)
return val;
},
set: function (newValue) {
if (newValue === val) return;
val = newValue;
console.log(val + "=>" + newValue)
// 通知所有订阅者
dep.notify();
}
});
}
/*
第一步
1,创建文档碎片,劫持所有dom节点,重绘dom节点
2,重绘dom节点,初始化文档碎片绑定数据 实现文档编译 compile
*/
function getDocumentFragment(node, vm) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
/*
while (child = node.firstChild)
相当于
child = node.firstChild
while (child)
*/
compile(child, vm);
flag.appendChild(child);
}
node.appendChild(flag);
}
function compile(node, vm) {
/*
nodeType 返回数字,表示当前节点类型
1 Element 代表元素 Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference
2 Attr 代表属性 Text, EntityReference
3 Text 代表元素或属性中的文本内容。
. . . 更多请查看文档
*/
if (node.nodeType === 1) {
// 获取当前元素的attr属性
var attr = node.attributes;
for (let i = 0; i < attr.length; i++) {
// nodeName 是attr属性 key 即名称 , 匹配自定义 v-m
if (attr[i].nodeName === 'v-m') {
// 获取当前值 即 v-m = "test" 里边的 test
let name = attr[i].nodeValue;
// 当前节点输入事件
node.addEventListener('keyup', function (e) {
vm[name] = e.target.value;
});
// 页面元素写值 vm.data[name] 即 vm.data['test'] 即 MVVM
node.value = vm.data[name];
//最后移除标签中的 v-m 属性
node.removeAttribute('v-m');
// 为每一个节点创建一个 watcher
new Watcher(vm, node, name, "input");
}
}
/*
继续递归调用 文档编译 实现 视图更新 ;
*/
if (child = node.firstChild) {
/*
if (child = node.firstChild)
相当于
child = node.firstChild
id(child)
*/
compile(child, vm);
}
}
if (node.nodeType === 3) {
let reg = /\{\{(.*)\}\}/;
if (reg.test(node.nodeValue)) {
let name = RegExp.$1.trim();
node.nodeValue = vm.data[name];
// 为每一个节点创建一个 watcher
new Watcher(vm, node, name, "text");
}
}
}
/*
创建一个构造函数,并生成实例化对象 vm
*/
function Vue(o) {
this.id = o.el;
this.data = o.data;
observe(this.data, this);
getDocumentFragment(document.getElementById(this.id), this);
}
var vm = new Vue({
el: 'app',
data: {
msg: 'HiSen',
test: 'Hello,MVVM'
}
});
</script>
</html>