我们知道的,常见的数据绑定的实现方法

1、数据劫持(vue):通过Object.defineProperty() 去劫持数据每个属性对应的getter和setter
2、脏值检测(angular):通过特定事件比如input,change,xhr请求等进行脏值检测。
3、发布-订阅模式(backbone):通过发布消息,订阅消息进行数据和视图的绑定监听。具体代码实现可以参考我github个人仓库overwrite->my-observer

一言不合先上代码和效果图吧
code

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>example</title>
<script src="./mvvm.js" charset="utf-8"></script>
</head>
<body>
<div id="mvvm">
<h2>{{b}}</h2>
<input type="text" x-model="a">
<input type="text" name="" value="" x-model="a">
<p x-html="a">{{ a }}</p>
<button type="button" name="button" x-on:click="testToggle">change b</button>
</div>
</body>
<script>
var vm = new MVVM({
el: '#mvvm',
data: {
a: 'test model',
b: 'hello MVVM',
flag: true
},
methods: {
testToggle: function () {
this.flag = !this.flag;
this.b = this.flag ? 'hello MVVM' : 'test success'
}
}
});
</script>
</html>

效果图

看完效果图之后,接下来我们直接搞事情吧

一、总体大纲

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

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

1、实现一个Observer,对数据进行劫持,通知数据的变化
2、实现一个Compile,对指令进行解析,初始化视图,并且订阅数据的变更,绑定好更新函数
3、实现一个Watcher,将其作为以上两者的一个中介点,在接收数据变更的同时,让Dep添加当前Watcher,并及时通知视图进行update
4、实现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, '=>', newVal);
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。
                                               
                            我不是老司机,我只是一个纯洁的开电动车的孩子

自己动手实现一个MVVM库的更多相关文章

  1. 创建你的第一个JavaScript库

    是否曾对Mootools的魔力感到惊奇?是否有想知道Dojo如何做到那样的?是否对jQuery感到好奇?在这个教程中,我们将了解它们背后的东西并且动手创建一个超级简单的你最喜欢的库. 我们其乎每天都在 ...

  2. Road to the future——伪MVVM库Q.js

    模仿Vuejs的伪MVVM库,下面是使用说明 项目地址:https://github.com/miniflycn/Q.js 相关项目:https://github.com/miniflycn/Ques ...

  3. 你不需要 jQuery,但你需要一个 DOM 库

    写这篇文章的目的,一方面是介绍一下自己编写的模块化 DOM 库 domq.js,另一方面是希望大家对 jQuery 有一个正确的认识,即使 jQuery 已经逐渐退出历史舞台,但是它的 API 将会以 ...

  4. 十二步创建你的第一个JavaScript库

    是否曾对Mootools的魔力感到惊奇?是否有想知道Dojo如何做到那样的?是否对jQuery感到好奇?在这个教程中,我们将了解它们背后的东西并且动手创建一个超级简单的你最喜欢的库. 我们其乎每天都在 ...

  5. 动手写一个简单版的谷歌TPU-矩阵乘法和卷积

    谷歌TPU是一个设计良好的矩阵计算加速单元,可以很好的加速神经网络的计算.本系列文章将利用公开的TPU V1相关资料,对其进行一定的简化.推测和修改,来实际编写一个简单版本的谷歌TPU.计划实现到行为 ...

  6. 自已实现一个UI库

    [2014年写一个UI库时写的几个文章,公布出来] 几年前的一个嵌入式的UI开发,使自己有机会接触到了UI的一些底层知识,尽管之前也开发过非常多Windows下的信息应用系统,也做非常多的界面开发,但 ...

  7. 自己动手编写一个VS插件(五)

    作者:朱金灿 来源:http://blog.csdn.net/clever101 继续编写VisualStudio插件.这次我编写的插件叫DevAssist(意思是开发助手).在看了前面的文章之后你知 ...

  8. [WPF 自定义控件]开始一个自定义控件库项目

    1. 目标 我实现了一个自定义控件库,并且打算用这个控件库作例子写一些博客.这个控件库主要目标是用于教学,希望通过这些博客初学者可以学会为自己或公司创建自定义控件,并且对WPF有更深入的了解. 控件库 ...

  9. 《动手实现一个网页加载进度loading》

    loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达.最常见的比如"转圈圈" ...

随机推荐

  1. linux c中select使用方法

    1.select函数作为定时器使用    it_value.tv_sec = 0;    it_value.tv_usec = 100000:    select(1,NULL,NULL,NULL,& ...

  2. springcloud中的API网关服务Zuul

    到目前为止,我们Spring Cloud中的内容已经介绍了很多了,Ribbon.Hystrix.Feign这些知识点大家都耳熟能详了,我们在前文也提到过微服务就是把一个大的项目拆分成很多小的独立模块, ...

  3. Git GUI可视化操作教程

    1.在本地新建版本库 首先,我们打开Git GUI是这样的一个界面,选择第一项,新建版本库.  然后选择你需要进行版本管理的项目路径,我选择了一个LoginDemo的项目.  当你创建了版本库的时候, ...

  4. Ubuntu下mysql使用

    1. 从网上安装 sudo apt-get install mysql-server.装完已经自动配置好环境变量,可以直接使用mysql的命令. 注:建议将/etc/apt/source.list中的 ...

  5. About Saliency Object Detection

    显著性对象检测综述 详见:http://mmcheng.net/zh/paperreading/ 一.    程明明等人的论文:Salient Object Detection: A Survey(简 ...

  6. java LinkedList(链表)

    LinkedList也像ArrayList一样实现了基本的List接口,但是它执行某些操作(在List的中间插入和移除)时比ArrayList更高效,但在随机访问方面却要逊色一些 LinkedList ...

  7. Java ListIterator(迭代器)

    LIstIterator是一个更加强大的Iterator的子类型,它只能用于各种List类的访问,尽管Iterator只能向前移动,但是ListIterator可以双向移动,它还可以产生相对于迭代器在 ...

  8. CentOS 安装codeblocks

    1. 安装wxWidgets .tar.bz2 [root@luozhonghua codeblocks]# cd wxWidgets- [root@luozhonghua wxWidgets-]# ...

  9. 2018-2019-2 网络对抗技术 20165301 Exp4 恶意代码分析

    2018-2019-2 网络对抗技术 20165301 Exp4 恶意代码分析 实验内容 系统运行监控 使用如计划任务,每隔一分钟记录自己的电脑有哪些程序在联网,连接的外部IP是哪里.运行一段时间并分 ...

  10. NOIP 2000 计算器的改良

    题面 NCL是一家专门从事计算器改良与升级的实验室,最近该实验室收到了某公司所委托的一个任务:需要在该公司某型号的计算器上加上解一元一次方程的功能.实验室将这个任务交给了一个刚进入的新手ZL先生. 为 ...