30行代码实现Javascript中的MVC
从09年左右开始,MVC逐渐在前端领域大放异彩,并终于在刚刚过去的2015年随着React Native的推出而迎来大爆发:AngularJS、EmberJS、Backbone、ReactJS、RiotJS、VueJS…… 一连串的名字走马观花式的出现和更迭,它们中一些已经渐渐淡出了大家的视野,一些还在迅速茁壮成长,一些则已经在特定的生态环境中独当一面舍我其谁。但不论如何,MVC已经并将持续深刻地影响前端工程师们的思维方式和工作方法。
很多讲解MVC的例子都从一个具体的框架的某个概念入手,比如Backbone的collection或AngularJS中model,这当然不失为一个好办法。但框架之所以是框架,而不是类库(jQuery)或者工具集(Underscore),就是因为它们的背后有着众多优秀的设计理念和最佳实践,这些设计精髓相辅相成,环环相扣,缺一不可,要想在短时间内透过复杂的框架而看到某一种设计模式的本质并非是一件容易的事。
这便是这篇随笔的由来——为了帮助大家理解概念而生的原型代码,应该越简单越好,简单到刚刚足以大家理解这个概念就够了。
1. MVC的基础是观察者模式,这是实现model和view同步的关键
为了简单起见,每个model实例中只包含一个primitive value值。
function Model(value) {
    this._value = typeof value === 'undefined' ? '' : value;
    this._listeners = [];
}
Model.prototype.set = function (value) {
    var self = this;
    self._value = value;
    // model中的值改变时,应通知注册过的回调函数
    // 按照Javascript事件处理的一般机制,我们异步地调用回调函数
    // 如果觉得setTimeout影响性能,也可以采用requestAnimationFrame
    setTimeout(function () {
        self._listeners.forEach(function (listener) {
            listener.call(self, value);
        });
    });
};
Model.prototype.watch = function (listener) {
    // 注册监听的回调函数
    this._listeners.push(listener);
};
// html代码:
<div id="div1"></div>
// 逻辑代码:
(function () {
var model = new Model();
var div1 = document.getElementById('div1');
model.watch(function (value) {
div1.innerHTML = value;
});
model.set('hello, this is a div');
})();
借助观察者模式,我们已经实现了在调用model的set方法改变其值的时候,模板也同步更新,但这样的实现却很别扭,因为我们需要手动监听model值的改变(通过watch方法)并传入一个回调函数,有没有办法让view(一个或多个dom node)和model更简单的绑定呢?
2. 实现bind方法,绑定model和view
Model.prototype.bind = function (node) {
    // 将watch的逻辑和通用的回调函数放到这里
    this.watch(function (value) {
        node.innerHTML = value;
    });
};
// html代码:
<div id="div1"></div>
<div id="div2"></div>
// 逻辑代码:
(function () {
var model = new Model();
model.bind(document.getElementById('div1'));
model.bind(document.getElementById('div2'));
model.set('this is a div');
})();
通过一个简单的封装,view和model之间的绑定已经初见雏形,即使需要在一个model上绑定多个view,实现起来也很轻松。注意bind是Function类prototype上的一个原生方法,不过它和MVC的关系并不紧密,笔者又实在太喜欢bind这个单词,一语中的,言简意赅,所以索性在这里把原生方法覆盖了,大家可以忽略。言归正传,虽然绑定的复杂度降低了,这一步依然要依赖我们手动完成,有没有可能把绑定的逻辑从业务代码中彻底解耦呢?
3. 实现controller,将绑定从逻辑代码中解耦
细心的朋友可能已经注意到,虽然讲的是MVC,但是上文中却只出现了Model类,View类不出现可以理解,毕竟HTML就是现成的View(事实上本文中从始至终也只是利用HTML作为View,javascript代码中并没有出现过View类),那Controller类为何也隐身了呢?别急,其实所谓的"逻辑代码"就是一个框架逻辑(姑且将本文的原型玩具称之为框架)和业务逻辑耦合度很高的代码段,现在我们就来将它分解一下。
如果要将绑定的逻辑交给框架完成,那么就需要告诉框架如何来完成绑定。由于JS中较难完成annotation(注解),我们可以在view中做这层标记——使用html的标签属性就是一个简单有效的办法。
function Controller(callback) {
    var models = {};
    // 找到所有有bind属性的元素
    var views = document.querySelectorAll('[bind]');
    // 将views处理为普通数组
    views = Array.prototype.slice.call(views, 0);
    views.forEach(function (view) {
        var modelName = view.getAttribute('bind');
        // 取出或新建该元素所绑定的model
        models[modelName] = models[modelName] || new Model();
        // 完成该元素和指定model的绑定
        models[modelName].bind(view);
    });
    // 调用controller的具体逻辑,将models传入,方便业务处理
    callback.call(this, models);
}
// html:
<div id="div1" bind="model1"></div>
<div id="div2" bind="model1"></div>
// 逻辑代码:
new Controller(function (models) {
var model1 = models.model1;
model1.set('this is a div');
});
就这么简单吗?就这么简单:在Controller中完成业务逻辑并对Model进行修改,Model的变化触发View的自动更新,怎么样,算得上一个有模有样的MVC吧?当然,这样的"框架"还不足以用于生产环境,不过如果它能或多或少地帮助到大家对于MVC的理解的话,博主就非常满足了。
整理后去掉注释的"框架"代码:
function Model(value) {
    this._value = typeof value === 'undefined' ? '' : value;
    this._listeners = [];
}
Model.prototype.set = function (value) {
    var self = this;
    self._value = value;
    setTimeout(function () {
        self._listeners.forEach(function (listener) {
            listener.call(self, value);
        });
    });
};
Model.prototype.watch = function (listener) {
    this._listeners.push(listener);
};
Model.prototype.bind = function (node) {
    this.watch(function (value) {
        node.innerHTML = value;
    });
};
function Controller(callback) {
    var models = {};
    var views = Array.prototype.slice.call(document.querySelectorAll('[bind]'), 0);
    views.forEach(function (view) {
        var modelName = view.getAttribute('bind');
        (models[modelName] = models[modelName] || new Model()).bind(view);
    });
    callback.call(this, models);
}
4. 一个简单的例子
下面请大家看一个简单例子,如何实现电子表
// html:
<span bind="hour"></span> : <span bind="minute"></span> : <span bind="second"></span>
// controller:
new Controller(function (models) {
function setTime() {
var date = new Date();
models.hour.set(date.getHours());
models.minute.set(date.getMinutes());
models.second.set(date.getSeconds());
}
setTime();
setInterval(setTime, 1000);
});
可以看出,controller中只负责更新model的逻辑,和view完全解耦;而view和model的绑定是通过view中的属性和框架中controller的初始化代码完成的,也没有出现在业务逻辑中;至于view的更新,也是通过框架中的观察者模式实现的。
  
后记:
笔者在学习flux和redux的过程中,虽然掌握了工具的使用方法,但只是知其然而不知其所以然,对ReactJS官方文档中一直强调的 "Flux eschews MVC in favor of a unidirectional data flow" 不甚理解,始终觉得单向数据流和MVC并不冲突,不明白为什么在ReactJS的文档中这二者会被对立起来,有他无我,有我无他(eschew,避开)。终于下定决心,回到MVC的定义上重新研究,虽然平日工作里大大咧咧复制粘贴,但是咱们偶尔也得任性一把,咬文嚼字一番,对吧?这样的方式也的确帮助了我对于这句话的理解,这里可以把自己的思考分享给大家:之所以觉得MVC和flux中的单向数据流相似,可能是因为没有区分清楚MVC和观察者模式的关系造成的——MVC是基于观察者模式的,flux也是,因此这种相似性的由来是观察者模式,而不是MVC和flux本身。这样的理解也在四人组的设计模式原著中得到了印证:"The first and perhaps best-known example of the Observer pattern appears in Smalltalk Model/View/Controller (MVC), the user interface framework in the Smalltalk environment [KP88]. MVC's Model class plays the role of Subject, while View is the base class for observers. "。
如果读者有兴趣在这样一个原型玩具的基础上继续拓展,可以参考下面的一些方向:
1. 实现对input类标签的双向绑定
2. 实现对controller所控制的scope的精准控制,这里一个controller就控制了整个dom树
3. 实现view层有关dom node隐藏/显示、创建/销毁的逻辑
4. 集成virtual dom,增加dom diff的功能,提高渲染效率
5. 提供依赖注入功能,实现控制反转
6. 对innerHTML的赋值内容进行安全检查,防止恶意注入
7. 实现model collection的逻辑,这里每个model只有一个值
8. 利用es5中的setter改变set方法的实现,使得对model的修改更加简单
9. 在view层中增加对属性和css的控制
10.支持类似AngularJS中双大括号的语法,只绑定部分html
……
一个完善的框架要经过无数的提炼和修改,这里只是最初最初的第一步,不过别忘了,我们的征程是星辰大海,哈哈
作者:ralph_zhu
时间:2016-02-15 14:30
原文:http://www.cnblogs.com/front-end-ralph/p/5190442.html
30行代码实现Javascript中的MVC的更多相关文章
- 30 行代码实现 JS 中的 MVC
		
一连串的名字走马观花式的出现和更迭,它们中一些已经渐渐淡出了大家的视野,一些还在迅速茁壮成长,一些则已经在特定的生态环境中独当一面舍我其谁.但不论如何,MVC已经并将持续深刻地影响前端工程师们的思维方 ...
 - 【干货理解】理解javascript中实现MVC的原理
		
理解javascript中的MVC MVC模式是软件工程中一种软件架构模式,一般把软件模式分为三部分,模型(Model)+视图(View)+控制器(Controller); 模型:模型用于封装与应用程 ...
 - 30行代码搞定WCF并发性能测试
		
[以下只是个人观点,欢迎交流] 30行代码搞定WCF并发性能 轻量级测试. 1. 调用并发测试接口 static void Main() { List< ...
 - 30行代码让你理解angular依赖注入:angular 依赖注入原理
		
依赖注入(Dependency Injection,简称DI)是像C#,java等典型的面向对象语言框架设计原则控制反转的一种典型的一种实现方式,angular把它引入到js中,介绍angular依赖 ...
 - 10分钟教你用python 30行代码搞定简单手写识别!
		
欲直接下载代码文件,关注我们的公众号哦!查看历史消息即可! 手写笔记还是电子笔记好呢? 毕业季刚结束,眼瞅着2018级小萌新马上就要来了,老腊肉小编为了咱学弟学妹们的学习,绞尽脑汁准备编一套大学秘籍, ...
 - Tensorflow快餐教程(1) - 30行代码搞定手写识别
		
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/lusing/article/details ...
 - Asp.Net Core-几行代码解决Razor中的嵌套if语句
		
MVC开发中,经常会遇到在razor中插入简单的逻辑判断. @if (clientManager.IsAdmin) { if (!Model.Topic.Top) { <a asp-action ...
 - 示例 - 10行代码在C#中获取页面元素布局信息
		
最近研究一个如何在网页定位验证码并截图的问题时, 用SS写了一段C#小脚本可以轻松获取页面任意元素的布局信息 (top, left, width, height). 10行功能代码, 觉得有点用, 现 ...
 - 数据结构 | 30行代码,手把手带你实现Trie树
		
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法和数据结构专题的第28篇文章,我们一起来聊聊一个经典的字符串处理数据结构--Trie. 在之前的4篇文章当中我们介绍了关于博弈论的 ...
 
随机推荐
- MongoDB Shard部署及Tag的使用
			
Shard部署 准备测试环境 为准备数据文件夹 Cd /home/tiansign/fanr/mongodb/Shard mkdir configdb1 configdb2 configdb3 mk ...
 - Sql Server之旅——第七站 为什么都说状态少的字段不能建索引
			
我们在学sqlserver的时候,大多教科书和前辈们都说状态少的字段不要建索引,由此带来的开销还不如不建索引,但是这句话有多少人真的知道, 或者说有多少人真的对此有比较深刻的理解,而不是听别人道听途说 ...
 - RedHat Linux 9.0的安装+入门指南(图文并茂)
			
一,准备工作1,购买或下载Redhat9的安装光盘(3张盘)或镜像文件2,在硬盘中至少留2个分区给安装系统用,挂载点所用分区推荐4G以上,交换分区不用太大在250M左右比较适合,文件系统格式不论,反正 ...
 - centos 安装依赖错误
			
出现下列错误: error: curl/curl.h: No such file or directory 出错原因:缺少libcurl-dev or libcurl-devel centOS上安装依 ...
 - 发现struct proc_dir_entry内核3.10.17移到internal中去了,倒
			
struct proc_dir_entry 原:2.6.38.8 在#include <linux/proc_fs.h> 现:3.10.17 在fs/proc/internal.h:str ...
 - 基于JAVA的全国天气预报接口调用示例
			
step1:选择本文所示例的接口"全国天气预报接口" url:https://www.juhe.cn/docs/api/id/39/aid/87step2:每个接口都需要传入一个参 ...
 - Eclipse调试Bug的七种常用技巧
			
1. 条件断点 断点大家都比较熟悉,在Eclipse Java 编辑区的行头双击就会得到一个断点,代码会运行到此处时停止. 条件断点,顾名思义就是一个有一定条件的断点,只有满足了用户设置的条件,代码才 ...
 - poj2486Apple Tree[树形背包!!!]
			
Apple Tree Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 9989 Accepted: 3324 Descri ...
 - NOIP1999邮票面值设计[搜索|DP]
			
题目描述 给定一个信封,最多只允许粘贴N张邮票,计算在给定K(N+K≤40)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值MAX,使在1-MAX之间的每一个邮资值都能得到 ...
 - Windows 常用 CMD 命令行介绍
			
看下面的链接: http://www.jianshu.com/p/76ff5d16f113