简单的实现Javascript的MVC

最近看了一篇文章,“30行代码实现Javascript中的MVC”,原文链接:http://www.jqsite.com/notes/1603205925.html ,受益良多,在此记录下学习的心得。 提到MVC,基本都会从一些框架开始,比如angularJs之类的,要在短时间内透过复杂的框架看到某一种设计模式并非是一件容易的事情。那么如何通过最简单的代码实现一个简单的MVC呢?

1.MVC的基础是观察者模式,这是实现Model与View同步的关键。

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 = value;
 });
 model.set('hello, this is a div');
})();

2.实现bind方法,绑定model与viewModel.prototype.bind = function (node) { // 将watch的逻辑和通用的回调函数放到这里

Model.prototype.bind = function (node) {
 // 将watch的逻辑和通用的回调函数放到这里
 this.watch(function (value) {
     node = value;
 });
};
// html代码:

// 逻辑代码:
(function () {
 var model = new Model();
 model.bind(document.getElementById('div1'));
 model.bind(document.getElementById('div2'));
 model.set('this is a div'); 
})();
 

3.实现controller,将绑定从逻辑代码中解耦

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:
// 逻辑代码:
new Controller(function (models) {
 var model1 = models.model1;
 model1.set('this is a div');
});

以下是根据我自己的理解,封装的代码,简单的实现了双向绑定和模仿了angularjs部分形式:

var app = (function(){
 var Model = function(value){
     this._v = value;
     this._listeners = [];
 }
 Model.prototype.set = function(value){
     var self = this;
     self._v = value;
     setTimeout(function(){
         self._listeners.forEach(function(listener){
             listener.call(this,value);
         })
     })
 }
 Model.prototype.watch = function(func){
     this._listeners.push(func);
 }
 Model.prototype.bind = function(node){
     var self = this;
     this.watch(function(value){
         if(node.tagName.toUpperCase()=="INPUT"  && !self.inputEvent){
             node.addEventListener("keyup",function(){
                 var _v = this.value;
                 if(_v != value){
                     self.set(_v);
                 }
                 self.inputEvent = 1;
             })
             node.value = value;
         }else{
             node = value;
         }
     })
 }
 function controller(controllername,callback){
     var models = {},
         search = typeof controllername=="string" ? "[controller=" + controllername + "]" : "[controller]",
         controller = document.querySelector(search),init = eval("("+controller.getAttribute("init")+")"),$scope = {};
     if(!controller) return;
     var views = Array.prototype.slice.call(controller.querySelectorAll("[bind]"),0);
     views.forEach(function(view){
         var modelname = view.getAttribute("bind");
         (models[modelname] = models[modelname] || new Model()).bind(view);
         $scope = createVisitors($scope,models[modelname],modelname);
     });
     for(var index in init){
         $scope[index] = init[index];
     }
     callback.call(this,$scope);
 }
 function createVisitors($scope,model,property){
     $scope.__defineSetter__(property,function(value){
         model.set(value);
     })
     return $scope;
 }
 return {
     controller : controller
 }
})();

一个使用的例子:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>MVC例子</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link href="" rel="stylesheet">
</head>
<body>
 <div>时钟</div>
 <div controller="clock">
     <span bind="hour"></span> : 
     <span bind="minutes"></span> :
     <span bind="seconds"></span> 
 </div>
 <br/> 
 <div>双向绑定</div>
 <div controller="myCtrl" init="{numb:2,num:'nihao'}">
     Input : <input type="text" bind="numb">
     <br/>Span : <span bind="numb"></span>
 </div>
 <script type="text/javascript">
  var app = (function(){
     var Model = function(value){
         this._v = value;
         this._listeners = [];
     }
     Model.prototype.set = function(value){
         var self = this;
         self._v = value;
         setTimeout(function(){
             self._listeners.forEach(function(listener){
                 listener.call(this,value);
             })
         })
     }
     Model.prototype.watch = function(func){
         this._listeners.push(func);
     }
     Model.prototype.bind = function(node){
         var self = this;
         this.watch(function(value){
             if(node.tagName.toUpperCase()=="INPUT"  && !self.inputEvent){
                 node.addEventListener("keyup",function(){
                     var _v = this.value;
                     if(_v != value){
                         self.set(_v);
                     }
                     self.inputEvent = 1;
                 })
                 node.value = value;
             }else{
                 node = value;
             }
         })
     }
     function controller(controllername,callback){
         var models = {},
             search = typeof controllername=="string" ? "[controller=" + controllername + "]" : "[controller]",
             controller = document.querySelector(search),init = eval("("+controller.getAttribute("init")+")"),$scope = {};
         if(!controller) return;
         var views = Array.prototype.slice.call(controller.querySelectorAll("[bind]"),0);
         views.forEach(function(view){
             var modelname = view.getAttribute("bind");
             (models[modelname] = models[modelname] || new Model()).bind(view);
             $scope = createVisitors($scope,models[modelname],modelname);
         });
         for(var index in init){
             $scope[index] = init[index];
         }
         callback.call(this,$scope);
     }
     function createVisitors($scope,model,property){
         $scope.__defineSetter__(property,function(value){
             model.set(value);
         })
         return $scope;
     }
     return {
         controller : controller
     }
  })();
  app.controller("myCtrl",function($scope){
     //code ...
  });
  app.controller("clock",function($scope){
     function setTime(){
         var date = new Date();
         $scope.hour = date.getHours();
         $scope.minutes = date.getMinutes();
         $scope.seconds = date.getSeconds();
         setTimeout(setTime,1000);
     }
     setTime();
  })
 </script>
</body>
</html>

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏zhangdd.com

ceph性能测试

该工具的语法为:rados bench -p <pool_name> <seconds> <write|seq|rand> -b <block size> -t...

29320
来自专栏较真的前端

Chrome开发者工具还有这些功能,你知道吗?

35180
来自专栏Alan's Lab

如何编写一个 jQuery 插件

https://github.com/zcfan/sket... 重写了本文的初步功能实现,支持一个页面多个画图板。但为简单起见,本文保持不变。

15840
来自专栏更流畅、简洁的软件开发方式

见到了“公司”定义一个Company类,那么见到了“字段”是不是也可定义一个Column类?

  既然见到了公司,我们可以定义一个Class Company ,那么我们见到了字段,是不是也可以定义一个Class ColumnInfo呢? 公司的描述信息类...

26890
来自专栏Spring相关

前端的CRUD增删改查的小例子

12830
来自专栏c#开发者

selenum参考手册中文翻译

Added by SpringSideTeam, last edited by SpringSideTeam on 2006-11-23  (view chan...

29860
来自专栏coding for love

JS常用设计模式解析01-单例模式

考虑实现如下功能,点击一个按钮后出现一个遮罩层。 原始办法:我们只需要实现一个创建遮罩层的函数并将其作为按钮点击的回调事件即可。如下:

16420
来自专栏Web 开发

来聊聊 DOM 中的Node、Element、Text

1所表示的ELEMENT_NODE 很常见,我们平时用的 div 等标签,其类型都是 ELEMENT_NODE。

10400
来自专栏从零开始学自动化测试

Selenium+python自动化82-只截某个元素的图

前言 selenium截取全图小伙伴们都知道,曾经去面试的时候,面试官问:如何截图某个元素的图?不要全部的,只要某个元素。。。小编一下子傻眼了, 苦心人,天不负...

54540
来自专栏web前端

JavaScript基础学习--02属性操作

一、思路 1、模拟手机聊天思路:      a.静态页面html+css,包括双发短信发送成功后的基本样式。      b.获取头像、输入框、发送按钮和聊天内...

21590

扫码关注云+社区

领取腾讯云代金券