自己动手实现一个MVVM库
我们知道的,常见的数据绑定的实现方法
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库的更多相关文章
- 创建你的第一个JavaScript库
是否曾对Mootools的魔力感到惊奇?是否有想知道Dojo如何做到那样的?是否对jQuery感到好奇?在这个教程中,我们将了解它们背后的东西并且动手创建一个超级简单的你最喜欢的库. 我们其乎每天都在 ...
- Road to the future——伪MVVM库Q.js
模仿Vuejs的伪MVVM库,下面是使用说明 项目地址:https://github.com/miniflycn/Q.js 相关项目:https://github.com/miniflycn/Ques ...
- 你不需要 jQuery,但你需要一个 DOM 库
写这篇文章的目的,一方面是介绍一下自己编写的模块化 DOM 库 domq.js,另一方面是希望大家对 jQuery 有一个正确的认识,即使 jQuery 已经逐渐退出历史舞台,但是它的 API 将会以 ...
- 十二步创建你的第一个JavaScript库
是否曾对Mootools的魔力感到惊奇?是否有想知道Dojo如何做到那样的?是否对jQuery感到好奇?在这个教程中,我们将了解它们背后的东西并且动手创建一个超级简单的你最喜欢的库. 我们其乎每天都在 ...
- 动手写一个简单版的谷歌TPU-矩阵乘法和卷积
谷歌TPU是一个设计良好的矩阵计算加速单元,可以很好的加速神经网络的计算.本系列文章将利用公开的TPU V1相关资料,对其进行一定的简化.推测和修改,来实际编写一个简单版本的谷歌TPU.计划实现到行为 ...
- 自已实现一个UI库
[2014年写一个UI库时写的几个文章,公布出来] 几年前的一个嵌入式的UI开发,使自己有机会接触到了UI的一些底层知识,尽管之前也开发过非常多Windows下的信息应用系统,也做非常多的界面开发,但 ...
- 自己动手编写一个VS插件(五)
作者:朱金灿 来源:http://blog.csdn.net/clever101 继续编写VisualStudio插件.这次我编写的插件叫DevAssist(意思是开发助手).在看了前面的文章之后你知 ...
- [WPF 自定义控件]开始一个自定义控件库项目
1. 目标 我实现了一个自定义控件库,并且打算用这个控件库作例子写一些博客.这个控件库主要目标是用于教学,希望通过这些博客初学者可以学会为自己或公司创建自定义控件,并且对WPF有更深入的了解. 控件库 ...
- 《动手实现一个网页加载进度loading》
loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达.最常见的比如"转圈圈" ...
随机推荐
- 关于sru源码class Model的parameters
class Model(nn.Module): def __init__(self, words, args): super(Model, self).__init__() self.args = a ...
- cancel_delayed_work和flush_scheduled_work【转】
转自:http://blog.chinaunix.net/uid-9688646-id-4052595.html 是不是觉得很玄?像思念一样玄?那好,我们来看点具体的,比如935行,INIT_DELA ...
- lombok使用说明
简介lombok 的官方网址:http://projectlombok.org/lombok 提供了简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 java 代码.特别是相对于 POJO, ...
- 关于iTerm2中颜色配置及快捷键使用技巧(亲测)
https://github.com/mbadolato/iTerm2-Color-Schemes http://chriskempson.com/projects/base16 (同事用的) 按照g ...
- 点击超链接打开本地QQ
2014年6月4日 10:20:18 张志斌 这个功能实际上是属于腾讯的推广项目"一键加群"功能: http://qun.qq.com/join.html 用户必须有自己的群,登录 ...
- [golang note] 环境搭建
LiteIDE(windows) • golang安装 ▶ 下载对应操作系统的版本并安装,下载地址:http://www.golangtc.com/download,譬如这里下载的是go1.6.win ...
- wpf 如果列表加载超多数据变的卡顿时,使用VirtualizingStackPanel
如果列表加载超多数据变的卡顿时 <ListBox > <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Virt ...
- Java @Override 注解
@Override注解,不是关键字,但可以当关键字使用,可以选择添加这个注解,在你不留心重载而并非复写了该方法时,编译器就会产生一条错误:The method doh(Milhouse) of typ ...
- Vue(SPA) WebPack模块化打包、SEO优化(Vue SSR服务端同构直出)、全浏览器兼容完整解决方案
白驹过隙,时光荏苒 大概去年这个时候写了angular 结合webpack的一套前端方案,今年此时祭出vue2结合webpack的一套前端方案. 明年的这个时候我又是在做什么... 读在最前面: 1. ...
- MVC:分页改进URL
http://localhost/?page=2 可以根据"可组合URL"创建一种更具吸引力的URL方案: http://localhost/page2 publi ...