简单的实现Javascript的MVC

时间:2022-04-21
本文章向大家介绍简单的实现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>