构建基础的JavaScript MVC(1)

构建基础的JavaScript MVC(一)

最近又把《基于MVC的JavaScript web富应用开发》过了一遍....看着自己的年度规划还有好多事没做(哈,要好好把握进度啊),但是也不能留旧账啊,所以还是总结一下啦,该书

对MVC做了基础的介绍,如果想往框架和类库方面探究,可以看看。

一、MVC基础

1、模型

 模型用来存放应用的数据对象;比如一个User模型;
   模型不必知晓试图和控制器的细节;
   模型最应该从应用中解藕;
   当控制器从服务器抓取数据或创建新纪录时,可以将数据包装成模型实例;

例如:       
    
    //good case
    //封装 & 实例方法
    var user = new User('name', 'tel', 'job');
    user.destroy();
    
    //bad case
    //因为destoryUser不在User的实例中
    var user = {name: 'vczero', tel: '132******', job: '前端'};
    destoryUser(user);

2、视图

 视图主要由html、css、js模板;
   模板中不应该存在逻辑,除了条件语句之外;

例如: 
   
    //render.js
    function render(data){
        //something to do
        //...
        view('userinfo', data);
    }
    
    //tpl.html
    <div>
        <span>{{data.name}}</span>
        <span>{{data.age}}</span>
    </div>

3、控制器

 控制器是视图间的纽带;
   控制器会给视图添加事件监听;

例如:    
 
    function userController(){
        //....
    }
    
    userController.add('#btn', 'click', function(){...});

二、构建类

1、构造函数:第一次实践

    //通过new和构造函数
    var User = function(name, nickname){
        this.name = name;
        this.nickname = nickname;
    }
    
    //实例化对象 good case
    var user = new User('vczero', '鬼谣');

2、模拟类库

    var Class = function(){
        var klass = function(){
            this.init.apply(this, arguments);
        };
        klass.prototype.init = function(){};    
        return klass;
    }
    
    var User = new Class();
    //初始化
    User.prototype.init = function(){
        console.log('------hello world-------');
    }
    //添加实例函数
    User.prototype.show = function(){
        console.log('show method');
    }
    //添加静态函数
    User.userCompare = function(){
        console.log('这里当然可以使用this比较');
    }
    var user = new User();
    
    //为了简洁,可以给类的prototype起个别名
    //例如User.fn = User.prototype;
    //User.fn.add = function(){....}

3、封装:更加清楚的表达 

    //上面的模拟类库表达不是很直观,因此采用新的方式
    //增加extend和include方法
    var Class = function(){
        var klass = function(){
            this.init.apply(this, arguments);
        };
        klass.prototype.init = function(){};
        
        //prototype别名
        klass.fn = klass.prototype;
        
        //类别名
        klass.fn.parent = klass;
        
        //增加类属性
        Klass.extend = function(obj){
            var extended = obj.extended;
            for(var i in obj){
                klass[i] = obj[i];
            }
            if(extended)extended(klass);
        };
        //增加实例属性
        klass.include = function(obj){
            var included = obj.included;
            for(var i in obj){
                klass.fn[i] = obj[i];
            }
            if(included) included(klass);
        };
        
        return klass;
    }
    
    //实例化对象
    var User = new Class();
    //静态方法
    User.extend({
        isName: function(){......},
        isNum: function(){......}
    });
    //实例方法
    User.include({
        add: function(){......},
        delete: function(){......}
    });

4、基于原型的继承

    var User = function(){};
    User.prototype.show = function(){
        console.log('----show----');
    };
    
    var Student = function(){};
    //Student继承了User
    Student.prototype = new User();
    Student.prototype.study = function(){
        console.log('-----study-----');
    };
    
    var stu = new Student();
    stu.show();
    stu.study();

5、给Class增加继承

    var Class = function(parent){
        var kalss = function(){
            this.init.apply(this, arguments);
        };
    
    if(parent){
        var subclass = function(){};
        subclass.prototype = parent.prototype;
        klass.prototype = new subclass();
    }
    
    klass.prototype.init = function(){};
    klass.fn = klass.prototype;
    klass.fn.parent = klass;
    //这里可以参考github.com/maccman/super.js
    klass._super = klass.__proto__;
    
    //增加类属性
        Klass.extend = function(obj){
            var extended = obj.extended;
            for(var i in obj){
                klass[i] = obj[i];
            }
            if(extended)extended(klass);
        };
        //增加实例属性
        klass.include = function(obj){
            var included = obj.included;
            for(var i in obj){
                klass.fn[i] = obj[i];
            }
            if(included) included(klass);
        };
        
        return klass;
    };
    

三、事件监听

1、基础事件

    /*
    * 事件 
    * 
    * */
    var Event = {
        //添加事件
        addEvent: function(el, type, callback, useCapture){
            if(el.addEventListener){
                el.addEventListener(type, callback, !useCapture);
            }else{
                el.attachEvent('on' + type, callback);
            }
            return callback;
        },
        //删除事件
        deleteEvent: function(el, type, callback, useCapture){
            if(el.removeEventListener){
                el.removeEventListener(type, callback, !useCapture);
            }else{
                el.detachEvent('on' + type, callback);
            }
        },
        //创建事件
        createEvent: function(types){
            if(document.createEvent){
                return document.createEvent(types);
            }else{
                return document.createEventObject();
            }
        },
        //触发事件
        fireEvent: function(el, type, args, event){
            args = args || {};
            if(el.dispatchEvent){
                event = document.createEvent('HTMLEvents');
                event.initEvent(type, true, true);
            }else{
                event = document.createEventObject();
            }
        
            for(var i in args){
                if(args.hasOwnProperty(i)){
                    event[i] = args[i];
                }
            }
            if(el.dispatchEvent){
                el.dispatchEvent(event);
            }else{
                el.fireEvent('on' + type, event);
            }
        }
    };

2、订阅 & 发布

    var PubSub = {
        sub: function(event, callback){
            var calls = this._callbacks || (this._callbacks = {});
            (this._callbacks[event] || (this._callbacks[event])).push(callback);
            return this;
        },
        
        publish: function(){
            var args = Array.prototype.slice.call(arguments, 0);
            var ev = args.shift();
            
            var list, calls, i, l;
            if(!(calls = this._callbacks)) return this;
            if(!(list = this._callback[ev])) return this;
            
            for(i = 0, l = list.length; i < l; i++){
                list[i].apply(this, args);
            }
            return this;
        }
    };

四、模型

1、模型的构建

    var User = {
        records: [],
        fetchRemote: function(){...}
    };
    
    以上代码有几个优点
    (0)子面量表达,那些属性和方法属于哪个对象,清晰明了;
    (1)属性保存在User命名空间下,可以减少命名冲突;
    (2)同时,我们可以很好的封装代码;
    But(世界上最怕的就是but了), 我们需要创建实例对象和实例方法。因此,我们改造:
    
    var User = function(name){
        this.name = name || 'vczero';
    }
    
    User.prototype.getName = function(){
        return this.name;
    }

2、ORM(对象关系映射)

ORM可以将模型和数据服务结合在一起,任何模型实例的改变都会发起一个ajax请求到服务器;或者说,将模型实例和HTML元素绑在一起。ORM的基础功能有CRUD数据、合法性校验、监听等。

    ORM可以将模型和数据服务结合在一起,任何模型实例的改变都会发起一个ajax请求到服务器;或者说,
    将模型实例和HTML元素绑在一起。ORM的基础功能有CRUD数据、合法性校验、监听等。
    1、基于原型的继承(prototype-based)
    Object.create传入一个参数,返回新一个新对象(新对象的原型就是传入参数),也即继承于参数对象
    if(typeof Object.create !== 'function'){
        Object.create = function(o){
            function F(){}
            F.prototype = o;
            return new F();
        }
    }
        
    现在,可以创建一个Model对象,将用于构建实例:
    var Model = {
        inherited: function(){},
        created: function(){},
        prototype: {
            init: function(){}
        },
        //返回一个新对象,这个对象继承于Model
        create: function(){
            var obj = Object.create(this);
            obj.parent = this;
            obj.prototype = obj.fn = Object.create(this.prototype);
            obj.created();
            this.inherited(obj);
            return obj;
        },
        //返回一个新对象,继承于Model.prototype,即Model的一个实例
        init: function(){
            var instance = Object.create(this.prototype);
            instance.parent = this;
            instance.init.apply(instance, arguments);
            return instance;
        }
    };
    
    var User = Model.create();
    var user = User.init();
    
    2、添加ORM属性
    在Jquery中,增加静态属性即类属性是:
     $.extend(Model, {
         check: function(){...}
     });
    在Jquery中,增加实例属性是: 
    $.extend(Model.prototype, {
         add: function(){...}
     });
     
     为了增加更多的属性到对象中,扩展Model属性,extend是扩展静态属性;include是扩展实例方法:
     var Model = {
         ...
         extend: function(o){
             var extended = o.extended;
             $.extend(this, o);
             if(extended) extended(this);
         },
         include: function(o){
             var included = o.included;
             $.extend(this.prototype, o);
             if(included) included(this);
         }
     }
     
     Model.include({
         init: function(opts){
             this.name = opts.name;
         }
     });
     
    var User = Model.create();
    var user = User.init({name: ''})
    
    3、保存对象
    需要能够对对象进行CRUD的操作,因此,增加records.
    Model.records = {};
    Model.include({
        newRecord: true,
        create: function(){
            this.newRecord = false;
            this.parent.records[this.id] = this;
        },
        destroy: function(){
            delete this.parent.records[this.id];
        },
        update: function(){
            this.parent.records[this.id] = this;
        },
        save: function(){
            this.newRecord ? this.create() : this. this.update();
        }
    });
    
    4、如果需要根据id来索引,可以创建GUID
    具体代码参考:https://github.com/vczero/guid/blob/master/guid_js.js
    此时,代码可以修改为:
    Model.extend({
        create: function(){
            if(!this.id) this.id = GUID.create();
            this.newRecord = false;
            this.parent.records[this.id] = this;
        }
    });
    5、提交数据模型到服务器
    为了持久化,需要讲数据模型添加到服务器,因此可以增加方法
    saveServer: function(url, callback){
        //do AJAX
    }

--by vczero(http://www.cnblogs.com/vczero/p/mvc_1.html)