自己动手实现一个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 的过程,我们会使用一些过渡动画来表达.最常见的比如"转圈圈" ...
随机推荐
- 前端JavaScript高级面试笔记
一.ES6 1.模块化 ES6通过export和import实现模块化 ES6的模块化的基本规则或特点, 欢迎补充: 1:每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件 ...
- list 复制
a=[1,2,3] b=a #b值改变,a也会改变 b[0]=90 print(a) [90,2,3] b=list(a) #这样修改b,a的值就不会改变
- IIS7配置HTTPS+默认访问https路径
一.下载证书(这里我使用的是阿里云免费的证书) 文件说明: 1. 1532858285913.key(证书私钥文件).1532858285913.pem(证书文件).1532858285913.pfx ...
- public private protect
public 公有 使用public意味着声明public之后的成员对每个人都是可用的 private 私有 除非必须公开底层实现细目,否则就应该将所有的域指定为private protect 继 ...
- FileInputStram入门
1.read() 读取一个字节 @Test public void test1() throws Exception{ //1.指定文件读取路径 String filePath = "E ...
- 移动端经常出现的兼容问题,谈谈移动端应用或者wap站的一些优化技巧和心得
移动端经常出现的兼容问题,谈谈移动端应用或者wap站的一些优化技巧和心得 1. 安卓浏览器看背景图片,有些设备会模糊. 因为手机分辨率太小,如果按照分辨率来显示网页,字会非常小,安卓手机 ...
- 整理OpenResty+Mysql+Tomcat+JFinal+Cannal+HUI
阿里云运维主机 118.190.89.22 26611 1.CentOS6.9下安装OpenResty 2.CentOS6.9下安装MariaDB10.2.11 3.使用Intellij IDEA把J ...
- Polly公共处理 -重试(Retry)
封装处理下Polly重试 private ILogger<PollyHelper> _logger; /// <summary> /// /// </summary> ...
- hdu 1698 线段树(成段替换 区间求和)
一条钩子由许多小钩子组成 更新一段小钩子 变成铜银金 价值分别变成1 2 3 输出最后的总价值 Sample Input11021 5 25 9 3 Sample OutputCase 1: The ...
- day10--协成\异步IO\缓存
协成(Gevent) 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程.CPU只认识线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将 ...