原生Js实现Vue双向绑定
Vue数据双向数据绑定是通过发布者-订阅者模式来实现的,在正常使用Vue的时候,调用data里的属性是这样的
1 | var vm = new Vue({ |

里面有一个set和get,这是使用了Object里面的defineProperty,我们新建一个空对象,使用一下这个函数
1 | var Person = {}; |


可以看出,我们对defineProperty函数的set和get方法进行了重写,而且结构也和Vue实现的结构差不多,所以Vue是通过defineProperty进行数据劫持的。
实现过程
- 实现一个监听者observe,用来劫持并监听所有的属性,如果有数据变动,就通知所有的订阅者
- 实现一个订阅者Watcher,收到属性变化通知后执行相应的函数,从而实现更新目的
实现一个observe
1 | function defineReactive(data,key,value){ |
实现订阅者
1 | function Dep () { |
把监听者和订阅者相关联
1 | function SelfVue (data, el, exp) { |
实现变换
1 | var ele = document.querySelector('#name'); |
整个的代码
1 | <!DOCTYPE html> |


确实在两秒后变换了数据,但是现在还没有解析器去解析{ { } }是直接利用innerHtml来替换里面的所有数据
所以,还要加一个Compile模板解析器
实现Compile
解析模板指令,替换数据,初始化视图
对应的节点绑定对应的更新函数,初始化相应的订阅器
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
113class Compile {
constructor(el, vm){
this.vm = vm;
this.el = document.querySelector(el.el);
this.fragment = null;
this.init();
}
init() {
if (this.el) {
this.fragment = this.nodeToFragment(this.el);
this.compileElement(this.fragment);
this.el.appendChild(this.fragment);
} else {
console.log('Dom元素不存在');
}
}
nodeToFragment (el) {
var fragment = document.createDocumentFragment();
var child = el.firstChild;
while (child) {
// 将Dom元素移入fragment中
fragment.appendChild(child);
child = el.firstChild
}
return fragment;
}
compileElement(el) {
var childNodes = el.childNodes;
var self = this;
[].slice.call(childNodes).forEach(function(node) {
var reg = /\{\{(.*)\}\}/;
var text = node.textContent;
if (self.isElementNode(node)) {
self.compile(node);
} else if (self.isTextNode(node) && reg.test(text)) {
self.compileText(node, reg.exec(text)[1]);
}
if (node.childNodes && node.childNodes.length) {
self.compileElement(node);
}
});
}
compile(node) {
var nodeAttrs = node.attributes;
var self = this;
Array.prototype.forEach.call(nodeAttrs, function(attr) {
var attrName = attr.name;
if (self.isDirective(attrName)) {
var exp = attr.value;
var dir = attrName.substring(2);
if (self.isEventDirective(dir)) { // 事件指令
self.compileEvent(node, self.vm, exp, dir);
} else { // v-model 指令
self.compileModel(node, self.vm, exp, dir);
}
node.removeAttribute(attrName);
}
});
}
compileText(node, exp) {
var self = this;
var initText = this.vm[exp];
this.updateText(node, initText);
new Watcher(this.vm, exp, function (value) {
self.updateText(node, value);
});
}
compileEvent(node, vm, exp, dir) {
var eventType = dir.split(':')[1];
var cb = vm.methods && vm.methods[exp];
if (eventType && cb) {
node.addEventListener(eventType, cb.bind(vm), false);
}
}
compileModel(node, vm, exp, dir) {
var self = this;
var val = this.vm[exp];
this.modelUpdater(node, val);
new Watcher(this.vm, exp, function (value) {
self.modelUpdater(node, value);
});
node.addEventListener('input', function(e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
self.vm[exp] = newValue;
val = newValue;
});
}
updateText (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
}
modelUpdater(node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
}
isDirective(attr) {
return attr.indexOf('v-') == 0;
}
isEventDirective(dir) {
return dir.indexOf('on:') === 0;
}
isElementNode (node) {
return node.nodeType == 1;
}
isTextNode(node) {
return node.nodeType == 3;
}
}
整合
1 | class SelfVue{ |
1 | <div id="app"> |


