对于vue双向数据绑定的理解
# 实现原理
vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty() 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。
- Observer 对所有数据的属性进行监听
- Compile 对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
- Watcher 作为连接Observer 跟 Compile 之间的桥梁, 能够订阅接收到每个属性变动的通知,执行相应的回调函数
# 模拟实现
const Dep = function () {
this.subs = [];
};
Dep.prototype = {
addSub: function (sub) {
this.subs.push (sub);
},
notify: function () {
this.subs.forEach(sub => {
sub.update();
});
},
};
const Watcher = function (vm, node, name) {
Dep.target = this;
this.name = name;
this.node = node;
this.vm = vm;
this.update();
Dep.target = null;
};
Watcher.prototype = {
update: function () {
this.get();
this.node.nodeValue = this.value;
},
get: function () {
this.value = this.vm[this.name];
},
};
const compile = function (node, vm) {
if (node.nodeType === 1) {
let attr = node.attributes;
for (let i = 0; i < attr.length; i++) {
if (attr[i].nodeName === 'v-model') {
let name = attr[i].nodeValue;
node.addEventListener('input', e => {
vm[name] = node.value;
});
node.value = vm[name];
node.removeAttribute ('v-model');
}
}
}
// Text 节点类型
if (node.nodeType === 3) {
if (/\{\{(.*)\}\}/.test (node.nodeValue)) {
let name = RegExp.$1;
name = name.trim();
node.nodeValue = vm[name];
new Watcher(vm, node, name);
}
}
};
const observe = data => {
if (!data || typeof data !== 'object') return;
Object.keys(data).forEach (key => defineReactive(data, key, data[key]));
};
const defineReactive = (data, key, value) => {
const dep = new Dep();
observe (value);
Object.defineProperty (data, key, {
get: function () {
if(Dep.target) dep.addSub(Dep.target);
return value;
},
set: function (newValue) {
console.log(数据已发生变化,新的值为${newValue});
value = newValue;
dep.notify();
},
});
};
function nodeToFragment (node, vm) {
let flag = document.createDocumentFragment();
let child;
while((child = node.firstChild)) {
compile(child, vm);
flag.appendChild(child);
}
return flag;
}
function Vue(options) {
let data = this.data = options.data;
observe(data, this);
let id = options.el;
let dom = nodeToFragment(document.getElementById(id), data);
document.getElementById(id).appendChild(dom);
}
let vm = new Vue({
el: 'app',
data: {
text: 'example text',
},
});
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
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