谢华良

2016-03-20 23:19

简单的实现Javascript的MVC

本文作者:IMWeb 谢华良 原文出处:IMWeb社区 未经同意,禁止转载

最近看了一篇文章,“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与view
    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>
    
4条评论

    您需要 注册 一个IMWeb账号或者 才能进行评论。