30行代码构建javascript 的MVC模式

MVC模式是软件工程中一种软件架构模式,一般把软件模式分为三部分,模型(Model)+视图(View)+控制器(Controller);

很多讲解MVC的例子都从一个具体的框架的某个概念入手,比如Backbone的collection或AngularJS中model,这当然不失为一个好办法。但框架之所以是框架,而不是类库(jQuery)或者工具集(Underscore),就是因为它们的背后有着众多优秀的设计理念和最佳实践,这些设计精髓相辅相成,环环相扣,缺一不可,要想在短时间内透过复杂的框架而看到某一种设计模式的本质并非是一件容易的事。

这便是这篇随笔的由来——为了帮助大家理解概念而生的原型代码,应该越简单越好,简单到刚刚足以大家理解这个概念就够了。

1.MVC的基础是观察者模式,这是实现model和view同步的关键

为了简单起见,每个model实例中只包含一个primitive value值。

 1 function Model(value) {
 2     this._value = typeof value === ‘undefined‘ ? ‘‘ : value;
 3     this._listeners = [];
 4 }
 5 Model.prototype.set = function (value) {
 6     var self = this;
 7     self._value = value;
 8     // model中的值改变时,应通知注册过的回调函数
 9     // 按照Javascript事件处理的一般机制,我们异步地调用回调函数
10     // 如果觉得setTimeout影响性能,也可以采用requestAnimationFrame
11     setTimeout(function () {
12         self._listeners.forEach(function (listener) {
13             listener.call(self, value);
14         });
15     });
16 };
17 Model.prototype.watch = function (listener) {
18     // 注册监听的回调函数
19     this._listeners.push(listener);
20 };
21
22 // html代码:
23 <div id="div1"></div>
24 // 逻辑代码:
25 (function () {
26     var model = new Model();
27     var div1 = document.getElementById(‘div1‘);
28     model.watch(function (value) {
29         div1.innerHTML = value;
30     });
31     model.set(‘hello, this is a div‘);
32 })();

借助观察者模式,我们已经实现了在调用model的set方法改变其值的时候,模板也同步更新,但这样的实现却很别扭,因为我们需要手动监听model值的改变(通过watch方法)并传入一个回调函数,有没有办法让view(一个或多个dom node)和model更简单的绑定呢?

2. 实现bind方法,绑定model和view

 1 Model.prototype.bind = function (node) {
 2     // 将watch的逻辑和通用的回调函数放到这里
 3     this.watch(function (value) {
 4         node.innerHTML = value;
 5     });
 6 };
 7
 8 // html代码:
 9 <div id="div1"></div>
10 <div id="div2"></div>
11 // 逻辑代码:
12 (function () {
13     var model = new Model();
14     model.bind(document.getElementById(‘div1‘));
15     model.bind(document.getElementById(‘div2‘));
16     model.set(‘this is a div‘);
17 })();

通过一个简单的封装,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的标签属性就是一个简单有效的办法。

 1 function Controller(callback) {
 2     var models = {};
 3     // 找到所有有bind属性的元素
 4     var views = document.querySelectorAll(‘[bind]‘);
 5     // 将views处理为普通数组
 6     views = Array.prototype.slice.call(views, 0);
 7     views.forEach(function (view) {
 8         var modelName = view.getAttribute(‘bind‘);
 9         // 取出或新建该元素所绑定的model
10         models[modelName] = models[modelName] || new Model();
11         // 完成该元素和指定model的绑定
12         models[modelName].bind(view);
13     });
14     // 调用controller的具体逻辑,将models传入,方便业务处理
15     callback.call(this, models);
16 }
17
18 // html:
19 <div id="div1" bind="model1"></div>
20 <div id="div2" bind="model1"></div>
21 // 逻辑代码:
22 new Controller(function (models) {
23     var model1 = models.model1;
24     model1.set(‘this is a div‘);
25 });

就这么简单吗?就这么简单:在Controller中完成业务逻辑并对Model进行修改,Model的变化触发View的自动更新,怎么样,算得上一个有模有样的MVC吧?当然,这样的”框架”还不足以用于生产环境,不过如果它能或多或少地帮助到大家对于MVC的理解的话,博主就非常满足了。

整理后去掉注释的”框架”代码:

 1 function Model(value) {
 2     this._value = typeof value === ‘undefined‘ ? ‘‘ : value;
 3     this._listeners = [];
 4 }
 5 Model.prototype.set = function (value) {
 6     var self = this;
 7     self._value = value;
 8     setTimeout(function () {
 9         self._listeners.forEach(function (listener) {
10             listener.call(self, value);
11         });
12     });
13 };
14 Model.prototype.watch = function (listener) {
15     this._listeners.push(listener);
16 };
17 Model.prototype.bind = function (node) {
18     this.watch(function (value) {
19         node.innerHTML = value;
20     });
21 };
22 function Controller(callback) {
23     var models = {};
24     var views = Array.prototype.slice.call(document.querySelectorAll(‘[bind]‘), 0);
25     views.forEach(function (view) {
26         var modelName = view.getAttribute(‘bind‘);
27         (models[modelName] = models[modelName] || new Model()).bind(view);
28     });
29     callback.call(this, models);[mw_shl_code=applescript,true]<span bind="hour"></span> : <span bind="minute"></span> : <span bind="second"></span>
30 // controller:
31 new Controller(function (models) {
32     function setTime() {
33         var date = new Date();
34         models.hour.set(date.getHours());
35         models.minute.set(date.getMinutes());
36         models.second.set(date.getSeconds());
37     }
38     setTime();
39     setInterval(setTime, 1000);
40 });
 1 function Model(value) {
 2     this._value = typeof value === ‘undefined‘ ? ‘‘ : value;
 3     this._listeners = [];
 4 }
 5 Model.prototype.set = function (value) {
 6     var self = this;
 7     self._value = value;
 8     setTimeout(function () {
 9         self._listeners.forEach(function (listener) {
10             listener.call(self, value);
11         });
12     });
13 };
14 Model.prototype.watch = function (listener) {
15     this._listeners.push(listener);
16 };
17 Model.prototype.bind = function (node) {
18     this.watch(function (value) {
19         node.innerHTML = value;
20     });
21 };
22 function Controller(callback) {
23     var models = {};
24     var views = Array.prototype.slice.call(document.querySelectorAll(‘[bind]‘), 0);
25     views.forEach(function (view) {
26         var modelName = view.getAttribute(‘bind‘);
27         (models[modelName] = models[modelName] || new Model()).bind(view);
28     });
29     callback.call(this, models);[mw_shl_code=applescript,true]<span bind="hour"></span> : <span bind="minute"></span> : <span bind="second"></span>
30 // controller:
31 new Controller(function (models) {
32     function setTime() {
33         var date = new Date();
34         models.hour.set(date.getHours());
35         models.minute.set(date.getMinutes());
36         models.second.set(date.getSeconds());
37     }
38     setTime();
39     setInterval(setTime, 1000);
40 });

4. 一个简单的例子

下面请大家看一个简单例子,如何实现电子表

// html:

可以看出,controller中只负责更新model的逻辑,和view完全解耦;而view和model的绑定是通过view中的属性和框架中controller的初始化代码完成的,也没有出现在业务逻辑中;至于view的更新,也是通过框架中的观察者模式实现的。

时间: 2024-10-02 22:12:01

30行代码构建javascript 的MVC模式的相关文章

30行代码实现JavaScript中的MVC

从09年左右开始,MVC逐渐在前端领域大放异彩,并终于在刚刚过去的2015年随着React Native的推出而迎来大爆发:AngularJS.EmberJS.Backbone.ReactJS.RiotJS.VueJS…… 一连串的名字走马观花式的出现和更迭,它们中一些已经渐渐淡出了大家的视野,一些还在迅速茁壮成长,一些则已经在特定的生态环境中独当一面舍我其谁.但不论如何,MVC已经并将持续深刻地影响前端工程师们的思维方式和工作方法. 很多讲解MVC的例子都从一个具体的框架的某个概念入手,比如B

60行代码:Javascript 写的俄罗斯方块游戏

先看效果图: 游戏结束图: javascript实现源码: <!doctype html> <html><head><title>俄罗斯方块</title> <meta name="Description" content="俄罗斯方块Javascript实现"> <meta name="Keywords" content="俄罗斯方块,Javascript,

280行代码:Javascript 写的2048游戏

2048 原作者就是用Js写的,一直想尝试,但久久未动手. 昨天教学生学习JS代码.不妨就做个有趣的游戏好了.2048这么火,是一个不错的选择. 思路: 1. 数组 ,2维数组4x4 2. 移动算法,移动后有数字的对齐,无数字(我用的0,但不显示)补齐. 移动前 移动后(注意程序合并了第一行2个2,并产生了新的2) 移动算法分2步: 第一步骤:移动 第二步骤:合并 移动代码参考: [html] view plaincopy function left(t,i) { var j; var len 

【分享】60行代码:Javascript 写的俄罗斯方块游戏

效果如下,可测试: javascript实现源码: <!doctype html> <html><head><title>俄罗斯方块</title> <meta name="Description" content="俄罗斯方块Javascript实现"> <meta name="Keywords" content="俄罗斯方块,Javascript,实现,短

30 行代码绘出你的微信朋友统计图

前言 大家好,这里是「brucepk」爬虫 系列教程.此文首发于「brucepk」公众号,欢迎大家关注.此系列教程以实例项目为材料进行分析,从项目中学习 python 爬虫,跟着我一起学习,每天进步一点点. 学编程是一件枯燥的事情,比较好的方法是在实际项目中学习成长.今天带来的是 30 行代码画出你的微信朋友的性别统计图. 最近发现一个有意思的库:itchat,itchat 是一个开源的微信个人号接口.今天就用 itchat 来统计自己微信好友性别的比例并用柱形图展示出来. 项目环境:pytho

JavaScript的MVC模式(转载)

本文介绍了模型-视图-控制器模式在 JavaScript 中的实现. 我喜欢 JavaScript,因为它是在世界上最灵活的语言之一.在 JavaScript 中,程序员可以根据自己的口味选择编程风格:面向过程或面向对象.如果你是一个重口味,JavaScript 一样可以应付自如:面向过程,面向对象,面向方面,使用 JavaScript 开发人员甚至可以使用函数式编程技术. 这篇文章中,我的目标是编写一个简单的 JavaScript 组件,来向大家展示一下 JavaScript 的强大.该组件是

Tensorflow快餐教程(1) - 30行代码搞定手写识别

去年买了几本讲tensorflow的书,结果今年看的时候发现有些样例代码所用的API已经过时了.看来自己维护一个保持更新的Tensorflow的教程还是有意义的.这是写这一系列的初心. 快餐教程系列希望能够尽可能降低门槛,少讲,讲透. 为了让大家在一开始就看到一个美好的场景,而不是停留在漫长的基础知识积累上,参考网上的一些教程,我们直接一开始就直接展示用tensorflow实现MNIST手写识别的例子.然后基础知识我们再慢慢讲. Tensorflow安装速成教程 由于Python是跨平台的语言,

Spring Boot 中 10 行代码构建 RESTful 风格应用

RESTful ,到现在相信已经没人不知道这个东西了吧!关于 RESTful 的概念,我这里就不做过多介绍了,传统的 Struts 对 RESTful 支持不够友好 ,但是 SpringMVC 对于 RESTful 提供了很好的支持,常见的相关注解有: @RestController @GetMapping @PutMapping @PostMapping @DeleteMapping @ResponseBody ... 这些注解都是和 RESTful 相关的,在移动互联网中,RESTful 得

数据结构——30行代码实现栈和模拟递归

本文始发于个人公众号:TechFlow,原创不易,求个关注 栈的定义 原本今天想给大家讲讲快速选择算法的,但是发现一连写了好几篇排序相关了,所以临时改了题目,今天聊点数据结构,来看看经典并且简单的数据结构--栈. 栈这个结构我想大家应该都耳熟能详,尤其是在很多地方将和堆并列在一起,称作"堆栈"就更广为人知了.但其实堆和栈本质上是两种不同的数据结构,我们不能简单地混为一谈.让我们先从比较简单的栈开始. 栈和队列的本质其实都是数组(严格地说是线性表).只不过我们在数组上增加了一些限制,使得