您的位置:首页 > 其它

自己动手实现一个mvvm库

2017-11-02 22:22 375 查看

一、总体大纲

要实现一个我们自己的mvvm库,我们首先需要做的事情不是写代码,而是整理一下思路,捋清楚之后再动手绝对会让你事半功倍。先上流程图,我们对着流程图来捋思路



如上图所示,我们可以看到,整体实现分为四步

实现一个
Observer
,对数据进行劫持,通知数据的变化

实现一个
Compile
,对指令进行解析,初始化视图,并且订阅数据的变更,绑定好更新函数

实现一个
Watcher
,将其作为以上两者的一个中介点,在接收数据变更的同时,让
Dep
添加当前
Watcher
,并及时通知视图进行
update


实现
MVVM
,整合以上三者,作为一个入口函数

二、动手时间

思路捋清楚了,接下来要做的事就是开始动手。


能动手的我决不动口

1、实现Observer

这里我们需要做的事情就是实现数据劫持,并将数据变更给传递下去。那么这里将会用到的方法就是
Object.defineProperty()
来做这么一件事。先不管三七二十一,咱先用用
Object.defineProperty()
试试手感。

function observe (data) {
if (!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(key => {
observeProperty(data, key, data[key])
})
}
function observeProperty (obj, key, val) {
observe(val);
Object.defineProperty(obj, key, {
enumerable: true,   // 可枚举
configurable: true, // 可重新定义
get: function () {
return val;
},
set: function (newVal) {
if (val === newVal || (newVal !== newVal && val !== val)) {
return;
}
console.log('数据更新啦 ', val, '=>', n
4000
ewVal);
val = newVal;
}
});
}


调用

var data = {
a: 'hello'
}
observe(data);


效果如下



看完是不是发现
JavaScript
提供给我们的
Object.defineProperty()
方法功能巨强大巨好用呢。



其实到这,我们已经算是完成了数据劫持,完整的
Observer
则需要将数据的变更传递给
Dep
实例,然后接下来的事情就丢给
Dep
去通知下面完成接下来的事情了,完整代码如下所示

/**
* @class 发布类 Observer that are attached to each observed
* @param {[type]} value [vm参数]
*/
function observe(value, asRootData) {
if (!value || typeof value !== 'object') {
return;
}
return new Observer(value);
}

function Observer(value) {
this.value = value;
this.walk(value);
}

Observer.prototype = {
walk: function (obj) {
let self = this;
Object.keys(obj).forEach(key => {
self.observeProperty(obj, key, obj[key]);
});
},
observeProperty: function (obj, key, val) {
let dep = new Dep();
let childOb = observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function() {
if (Dep.target) {
dep.depend();
}
if (childOb) {
childOb.dep.depend();
}
return val;
},
set: function(newVal) {
if (val === newVal || (newVal !== newVal && val !== val)) {
return;
}
val = newVal;
// 监听子属性
childOb = observe(newVal);
// 通知数据变更
dep.notify();
}
})
}
}
/**
* @class 依赖类 Dep
*/
let uid = 0;
function Dep() {
// dep id
this.id = uid++;
// array 存储Watcher
this.subs = [];
}
Dep.target = null;
Dep.prototype = {
/**
* [添加订阅者]
* @param  {[Watcher]} sub [订阅者]
*/
addSub: function (sub) {
this.subs.push(sub);
},
/**
* [移除订阅者]
* @param  {[Watcher]} sub [订阅者]
*/
removeSub: function (sub) {
let index = this.subs.indexOf(sub);
if (index !== -1) {
this.subs.splice(index ,1);
}
},
// 通知数据变更
notify: function () {
this.subs.forEach(sub => {
// 执行sub的update更新函数
sub.update();
});
},
// add Watcher
depend: function () {
Dep.target.addDep(this);
}
}
// 结合Watcher
/**
* Watcher.prototype = {
*   get: function () {
*     Dep.target = this;
*     let value = this.getter.call(this.vm, this.vm);
*     Dep.target = null;
*     return value;
*   },
*   addDep: function (dep) {
*     dep.addSub(this);
*   }
* }
*/


至此,我们已经实现了数据的劫持以及notify数据变化的功能了。

2、实现Compile

按理说我们应该紧接着实现
Watcher
,毕竟从上面代码看来,
Observer
Watcher
关联好多啊,但是,我们在捋思路的时候也应该知道了,
Watcher
Compile
也是有一腿的哦。所以咱先把
Compile
也给实现了,这样才能更好的让他们3P。



我不是老司机,我只是一个纯洁的开电动车的孩子
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: