手写vue

  目录

实现一个基础功能的vue

手写vue

实现一个非常简化版vue,暂时实现了v-text,v-html,v-model,@eventName指令,还有插值表达式{{}},
一共写了4个类去实现,Vue是主类,Dep依赖收集类,Watcher是观察者类,Dep依赖收集去触发Watcher观察者定义的的方法,Compile类是用来解析html的dom元素的,解析的时候向对应的数据添加依赖,这样数据改变视图跟着改变。
下面是代码:

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
class Vue {
constructor(option) {
this.el = option.el && document.querySelector(option.el);
this.$data = option.data;
this.$methods = option.methods;
this.init();
}
init() {
this.observer(this.$data);
new Compile(this.el, this);
}
// 给data值设置get,set的数据劫持
observer(daObj) {
if(typeof daObj === 'object') {
for(let key in daObj) {
let value = daObj[key];
if(value && typeof value === 'object') {
this.observer(value);
}else {
let dep = new Dep();
Object.defineProperty(daObj, key, {
get() {
Dep.target && dep.push(Dep.target);
return value;
},
set(val) {
value = val;
// 这里注意。依赖触发要在value=val的下边
dep.dispatch();
}
});
}
}
}
}
}
// 依赖类
class Dep {
constructor() {
this.watchers = [];
}
push(watcher) {
this.watchers.push(watcher);
}
dispatch() {
this.watchers.forEach(watcher=> {
watcher && watcher.callback && watcher.callback();
});
}
}
// 观察者类
class Watcher {
constructor(callback) {
Dep.target = this;
this.callback = callback;
}
}
// 编译模板字符串类
class Compile {
constructor(el, vm) {
this.parseHtml(el, vm);
}
parseHtml(el, vm) {
Array.from(el.childNodes).forEach(item=> {
// 处理带有指令的节点元素
this.updateDirect(item, vm);
if(item.nodeType === 1 && item.childNodes.length>0) {
// 递归解析
this.parseHtml(item, vm);
}else if(item.nodeType === 3 && /\{\{(.*)\}\}/.test(item.textContent)) {
let epx = RegExp.$1;
this.updateText(item, vm, epx);
new Watcher(()=> {
this.updateText(item, vm, epx);
});
eval('vm.$data.' + epx);
Dep.target = null;
}
});
}
// 判断是否是指令节点
updateDirect(node, vm) {
// 如果元素节点的属性是一个json对象
if(typeof node.attributes === 'object') {
// 转换成数组
let attrs = Array.from(node.attributes);
if(attrs.length > 0) {
for(let i=0; i<attrs.length; i++) {
// v-xxx指令
if(/^v\-.+/.test(attrs[i].name)) {
let handleStr = attrs[i].name.substring(2);
let epx = attrs[i].value;
this['dir'+handleStr](node, vm, epx);
}
// @事件
if(/^\@.+/.test(attrs[i].name)){
let eventName = attrs[i].name.substring(1);
let callbackName = attrs[i].value;
this['eventHandler'](node, vm, eventName, callbackName);
}
}
}
}
}
// 处理指令节点数据响应
dirtext(node, vm, epx) {
this.updateText(node, vm, epx);
new Watcher(()=> {
this.updateText(node, vm, epx);
});
eval('vm.$data.' + epx);
Dep.target = null;
}
dirhtml(node, vm, epx) {
this.updateHtml(node, vm, epx);
new Watcher(()=> {
this.updateHtml(node, vm, epx);
});
eval('vm.$data.' + epx);
Dep.target = null;
}
dirmodel(node, vm, epx) {
this.updateValue(node, vm, epx);
new Watcher(()=> {
this.updateValue(node, vm, epx);
});
eval('vm.$data.' + epx);
Dep.target = null;
// 添加input事件
node.addEventListener('input', (ev)=> {
eval('vm.$data.' + epx + '="' + ev.target.value + '"');
}, false);
}
// 处理指令事件
eventHandler(node, vm, eventName, callbackName) {
node.addEventListener(eventName, vm.$methods[callbackName]||loop, false);
}
// 更改元素节点的textContent
updateText(node, vm, epx) {
node.textContent = eval('vm.$data.'+epx);
}
// 更改元素节点的innerHTML
updateHtml(node, vm, epx) {
node.innerHTML = eval('vm.$data.'+epx);
}
// 更改input元素的value
updateValue(node, vm, epx) {
node.value = eval('vm.$data.'+epx);
}
}
var loop = function() {}

代码不多,但是基本功能已经实现。
代码点这里