用了两年左右的vue,虽然看过vue的源码,推荐黄轶大佬的vue源码分析,相当到位。从头梳理了vue的实现过程。周末又看了一个公开课的vue源码分析,想着自己是不是也可以写一个来实现,说干就干,开始coding!
目前最新版本的vue内部依然使用了Object.defineProperty()来实现对数据属性的劫持,进而达到监听数据变动的效果。
- 需要数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者。
- 需要指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
- 一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。
- mvvm入口函数,整合以上三者,实现数据响应。
相信看过vue官网的小伙伴们一定看过下面这张图吧,解释了vue是如何实现响应式的数据绑定。
Observer类的实现
主要利用了Object.defineProperty()这个方法,对数据进行遍历,给每一个对象都添加了getter()和setter().主要代码如下:
1 | class Observer{ |
经过以上的方法,我们就劫持到了数据属性。
Compile类的实现
主要用来解析各种指令,比如v-modal,v-on:click等指令。然后将模版中的变量替换成数据,渲染view,将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据发生变动,收到通知,更新视图。
1 | class Compile{ |
Watcher类的实现
作为链接的桥梁,链接了compile和observer。添加订阅者,当检测到属性发生变化,接收到dep.notify()的通知的时候,就执行自身的update()方法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
53class Watcher{
constructor(vm, expOrFn, cb){
this.cb = cb;
this.vm = vm;
this.expOrFn = expOrFn;
this.depIds = {};
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = this.parseGetter(expOrFn);
}
this.value = this.get();
}
update(){
this.run();
}
run(){
var value = this.get();
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
}
addDep(dep){
if (!this.depIds.hasOwnProperty(dep.id)) {
dep.addSub(this);
this.depIds[dep.id] = dep;
}
}
get() {
Dep.target = this;
var value = this.getter.call(this.vm, this.vm);
Dep.target = null;
return value;
}
parseGetter(exp){
if (/[^\w.$]/.test(exp)) return;
var exps = exp.split('.');
return function(obj) {
for (var i = 0, len = exps.length; i < len; i++) {
if (!obj) return;
obj = obj[exps[i]];
}
return obj;
}
}
}
mvvm实现
MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化,触发视图更新;视图交互变化(input) 触发数据model变更的双向绑定效果。
1 | class Mvvm{ |
基本上就大功告成了,大部分代码都是参考了vue源码的实现,学着读源码吧,体会vue设计的优雅。顺便推荐一个github读源码的chrome插件:octotree.本文完整代码请查看github
本文同步发表于掘金/segmentfault
顺便说一句,最近开始找工作了,坐标北京,如果各位大佬有机会,望推荐下哈,在此先行谢过!