zoukankan      html  css  js  c++  java
  • “计算机之子”的MVVM框架源码学习笔记

    随着avalon v2项目的启动,我又开始学习业内的各个MVVM框架。在一次偶然机会,幸运接触到计算机之子winter-cn的MVVM源码,需要认真学习一下。

    不过,这放出来是比较早期的源码,现在可能改进很多,膜拜地址:http://shaofei.name/mvvm/

    计算机之子的MVVM现在只支持非常高级的浏览器,还在使用IE678这样破浏览器,就免进吧,人家的高瞻远瞩岂非尔等屌丝所能想象的!

    他的框架由三个文件组成,分别是EventSource.js,ViewModel.js,HTMLTemplate.js。

    EventSource其实就可以看作为W3C的EventTarget类,是提供观察者模式的机制,没什么好说的。

    function EventSource() {
        var eventHandlers = {};
        this.addEventListener = function (type, handler) {//绑定事件
            if (!eventHandlers[type]) {
                eventHandlers[type] = [];
            }
            eventHandlers[type].push(handler);
        };
        this.removeEventListener = function (type, handler) {//卸载事件
            if (!eventHandlers[type]) {
                return;
            }
            eventHandlers[type] = eventHandlers[type].filter(function (f) {
                return f != handler;
            })
        };
        this.dispatchEvent = function (e) {//派发事件
            if (eventHandlers.hasOwnProperty(e.type)) {
                eventHandlers[e.type].forEach(function (f) {
                    f.call(this, e);
                })
            }
            if (this["on" + e.type]) {//??这个有用吗,没看到调用处
                this["on" + e.type](e);
            }
        }
    }
    

    ViewModel.js里面提供了两个EventSource的子类,分别叫做ViewModel与ArrayViewModel,功效类于backbone的Model与Collection,或knouckout的ko.observable与ko.observableArray。不过backbone这样的垃圾怎么能与MVVM这样的高级货相提并论呢,死到一边凉快吧!

    
    function ViewModel(data,parent) {
        if(data instanceof Array)//由于只针对当前页面,instanceof就足够了,不过既然不支持IE,可以用上Array.isArray
            return new ArrayViewModel(data,parent);
        var children = {};
        var me = this;
        for(var p in data) {
            if(data.hasOwnProperty(p)) {
                void function(p){//一个闭包
                    //通过属性描述符将用户的对象的属性变成ViewModel实例的具有访问器特性的属性
                    Object.defineProperty(this,p,{
                        get:function(){//当用户使用“aaa.bbb”来访问此属性时调用此函数
                            if(typeof data[p] == "object")
                                return children[p];
                            else return data[p];
                        },
                        set:function(v){//当用户使用“aaa.bbb = v”进行赋值时调用此函数
                            data[p] = v ;
                            if(typeof data[p] == "object") {//这里没有必要吧,★★★★处已经写过了
                                children[p] = new ViewModel(data[p]);
                                children[p].addEventListener("propertyChange",function(e){
                                    me.dispatchEvent({
                                        type:"propertyChange",
                                        propertyName:p,
                                        path:p+"."+e.path
                                    });
                                })
                            }
                            //同时向它的订阅者派发此事件,事件对象只是一个普通对象,分别描述事件类型,属性名,与属性路径(即此属性是属于某个属底下)
                            this.dispatchEvent({
                                type:"propertyChange",
                                propertyName:p,
                                path:p
                            });
                        }
                    });
                    if(typeof data[p] == "object") {//★★★★如果属性又是一个对象,则递归一下。不过应该判定值为null的情况!
                        children[p] = new ViewModel(data[p]);
                        //为它的属性绑定propertyChange事件
                        children[p].addEventListener("propertyChange",function(e){
                            me.dispatchEvent({
                                type:"propertyChange",
                                propertyName:p,
                                path:p+"."+e.path
                            });
                        })
                    }
                }.call(this,p);
            }
        }
        EventSource.call(this);
    }
    

    ArrayViewModel已ViewModel大量小异,就是换一种循环方式:

    function ArrayViewModel(data,parent) {
        var me = new Array(data.length);
        var children = {};
        for(var i = 0; i < data.length; i++) {
            void function(p){
                Object.defineProperty(this,p,{
                    get:function(){
                        if(typeof data[p] == "object")
                            return children[p];
                        else return data[p];
                    },
                    set:function(v){
                        data[p] = v ;
                        if(typeof data[p] == "object") {//我还是觉得这里没有必要
                            children[p] = new ViewModel(data[p]);
                            children[p].addEventListener("propertyChange",function(e){
                                me.dispatchEvent({
                                    type:"propertyChange",
                                    propertyName:p,
                                    path:p+"."+e.path
                                });
                            })
                        }
                        this.dispatchEvent({
                            type:"propertyChange",
                            propertyName:p,
                            path:p
                        });
                    }
                });
                if(typeof data[p] == "object") {
                    children[p] = new ViewModel(data[p]);
                    children[p].addEventListener("propertyChange",function(e){
                        me.dispatchEvent({
                            type:"propertyChange",
                            propertyName:p,
                            path:p+"."+e.path
                        });
                    })
                }
            }.call(me,i);
        }
        EventSource.call(me);
        return me;
    }
    

    我的建议是把它们压缩成这样:

    
    function defineProperty(me, children, data, p){
        Object.defineProperty(me, p,{
            get:function(){
                if(typeof data[p] == "object")
                    return children[p];
                else return data[p];
            },
            set:function(v){
                data[p] = v ;
                me.dispatchEvent({
                    type:"propertyChange",
                    propertyName:p,
                    path:p
                });
            }
        });
        if(typeof data[p] == "object") {//犹豫要不要识别null
            children[p] = new ViewModel(data[p]);
            children[p].addEventListener("propertyChange",function(e){
                me.dispatchEvent({
                    type:"propertyChange",
                    propertyName:p,
                    path:p+"."+e.path
                });
            })
        }
    }
    
    //这个new不new都没所谓!
    function ArrayViewModel(data,parent) {
        var me = new Array(data.length);
        for(var i = 0; i < data.length; i++) {
            defineProperty(me, {}, data, i);
        }
        EventSource.call(me);
        return me;
    }
    
    function ViewModel(data,parent) {
        if( Array.isArray(data))
            return new ArrayViewModel(data,parent);
        for(var p in data) {
            if(data.hasOwnProperty(p)) {
                defineProperty(this, {}, data, p);
            }
        }
        EventSource.call(this);
    }
    

    HTMLTemplate.js,偶表示完全度很低。从源码观察,发现大神都喜欢把所有代码塞到一个构造器中,亚历山大!

    里面有个HTMLTemplate类,传入一个HTML字符串,通过parse,返回一个文档碎片对象,并在体内保留一个parameters对象,等待用户调用它的apply方法,将VM实例传进来,为它绑定propertyChange事件!不过propertyChange只是一个自定义事件,用户在控件上操作还得依赖原生事件,有关如何绑定原生事件,绑定何种原生的事件的指令都写在模板上,它会在parse时分析出来,逐个绑定好!

    <script type="text/xhtml-template" id="t">
    
        <div style="background:rgb(${r},${g},${b});100px;height:100px;"></div>
        <input value="${r|input}" type="text"/>
        <input value="${g|input}" />
        <input value="${b|input}" />
    
    </script>
    

    ${r|input}这里就是它的指令,通过input事件监听input元素的value值,它对应的是VM的r属性。

    
    function HTMLTemplate(str){
        var input = null;
        var EOF = {};
        var element = null;
        var attr = "";
        var attributeNode = null;
        var state = data;
        var text = null;
        var tag = "";
        var errors = [];
        var isEndTag = false;
        var stack = [];
        var i;
        //最有用的三个东东
        var attrSetter = null;
        var parameterName = null;
        var parameters = null;
    
        function AttributeSetter(attributeNode) {
            this.parts = [];
            //用于拼凑成属性值
            this.appendPart = function(part){
                this.parts.push(part);
            }
            this.apply = function(){//赋值
                attributeNode.value = this.parts.join("");
            }
        }
    
        function consumeCharacterReference(additionalAllowedCharacter){ /*略*/  }
        function unconsume(n) { /*略*/  }
        function consume(n) { /*略*/   }
        function next(n) { /*略*/   }
        function error(){
        }
        function _assert(flag) { /*略*/   }
        var data = function(c){/*略*/   };
    
        var tagOpen = function(c){ /*略*/ };
        var endTagOpen = function(c){ /*略*/  };
        var tagName = function(c){ /*略*/    };
    
        var beforeAttributeName = function(c){ /*略*/   };
        var attributeName = function(c){/*略*/
        };
        var afterAttributeName = function(c){ /*略*/   };
        var beforeAttributeValue = function(c){ /*略*/  };
        var attributeValueDQ = function(c){
            if(c=="\"") {
                if(attrSetter) {//收集属性值碎片
                    attrSetter.appendPart(attributeNode.value);
                }
                /*略*/
            }
            /*略*/
        };
        var attributeValueSQ = function(c){
            if(c=="\'") {
                if(attrSetter) {//收集属性值碎片
                    attrSetter.appendPart(attributeNode.value);
                }
                /*略*/
            }
            /*略*/
        };
        var attributeValueUQ = function(c){
            if(c=="\n"||c=="\f"||c=="\t"||c==" ") {
                if(attrSetter) {//收集属性值碎片
                    attrSetter.appendPart(attributeNode.value);
                }
                /*略*/
            }
            /*略*/
        };
        var afterAttributeValueQ = function(c){ /*略*/ };
        var selfclosingStartTag = function(c){ /*略*/  };
        var afterDollarInText = function(c) { /*略*/  };
        var parameterInText = function(c) {/*处理innerText中的指令*/
            if(c=="}") {
                text = document.createTextNode("");
                var name = parameterName.join("")
                if(parameters[name])
                    parameters[name].push(text);//放进parameters中,这只是一个普通的文本节点
                else parameters[name] = [text];
                element.appendChild(text);
                parameterName = [];
                text = null;
                return data;
            }
            else {
                if(parameterName===null)
                    parameterName = [];
                parameterName.push(c);//拼凑属性名
                return parameterInText;
            }
        }
        var afterDollarInAttributeValueDQ = function(c) { /*略*/   }
        var afterDollarInAttributeValueSQ = function(c) {/*略*/    }
        var afterDollarInAttributeValueUQ = function(c) {/*略*/   }
        var parameterInAttributeValueDQ = function(c) {
            if(c=="}") {
                if(!attrSetter) {
                    attrSetter = new AttributeSetter(attributeNode);
                }
                attrSetter.appendPart(attributeNode.value);
                attributeNode.value = "";
                //这是一个特别的对象,拥有textContent,对textContent操作会引起连锁反应
                var text = {
                    setter:attrSetter,
                    value:"",
                    set textContent(v){
                        this.value = v;
                        this.setter.apply();
                    },
                    toString:function(){ return this.value;}
                };
                var parameterAttr = parameterName.join("").split("|")
                var name = parameterAttr[0]
                if(parameters[name])//放进parameters中,
                    parameters[name].push(text);
                else parameters[name] = [text];
                parameterName = [];
                attrSetter.appendPart(text);
                text = null;
    
                if(parameterAttr[1]) {
                    void function(element,attributeName){
                        //这里是一切的起点,当前属性绑定的宿主,input元素,它个属性绑定都指明要监听的属性与用什么监听
                        // <input value="${r|input}" type="text"/>
                        element.addEventListener(parameterAttr[1],function(){
                            console.log("element.value= "+element.value)
                            setBack(name,element[attributeName])
                        },false);
                    }(element,attributeNode.name);
                }
    
                return attributeValueDQ;
            }
            else {
                if(parameterName===null)
                    parameterName = [];
                parameterName.push(c);//拼凑属性名
                return parameterInAttributeValueDQ;
            }
        }
        var parameterInAttributeValueSQ = function(c) {/*作用与parameterInAttributeValueDQ差不多*/ }
        var parameterInAttributeValueUQ = function(c) {/*作用与parameterInAttributeValueDQ差不多*/ }
    
        function parse(){
            //开始分析,不过它是由apply调用的
            input = str.split("");
            input.push(EOF);
            var root = document.createDocumentFragment();
    
            state = data;
            element = root;
            stack = [];
    
            i = 0;
            while(i<input.length) {
                state = state(input[i++]);
            }
            return root;
        }
    
        var fragment = null;
        var setBack = function(){};
    
        this.apply = function(obj) {
            input = null;
            element = null;
            attr = "";
            attributeNode = null;
            state = data;
            text = null;
            tag = "";
            errors = [];
            isEndTag = false;
            stack = [];
            i;
            parameters = Object.create(null);
            fragment = parse(str);//一个文档碎片
            this.bind(obj);
            setBack = function(name,value) {
                //这里是回调
                obj[name] = value;
            }
            if(obj.addEventListener) {
                //obj为一个VM
                obj.addEventListener("propertyChange",function(e){
                    //然后去遍历parameters对象,它每个值是一个text对象
                    //当我们执行 textNode.textContent = "xxx"时,textContent为一个setter,它会触及其setter成员的apply方法
                    //setter成员其实是一个AttributeSetter类的实例,apply方法会将它引用的一个特性节点(nodeType=2)的值改变
                    parameters[e.propertyName].forEach(function(textNode){
                        textNode.textContent = obj[e.propertyName];
                    });
                },false);
            }
    
            return fragment;
        };
        Object.defineProperty(this,"fragment",{
            getter:function(){
                return fragment;
            }
        });
        this.bind = function(obj) {
            if(fragment==null)
                return;
            //非常重要的一步,把parse过程中收集好的parameters,通过textContent将VM的值赋上去
            Object.keys(parameters).forEach(function(prop,i){
                parameters[prop].forEach(function(textNode){
                    textNode.textContent = obj[prop];
                });
            });
        };
    }
    

    单从现在放出的源码,它的功能还是比较薄弱,期待更完整的版本吧!

  • 相关阅读:
    Delphi编写星光效果
    网格动画
    在窗体边框上画图
    algebra单元
    CMOS单元
    类似于Split(VB)的函数
    利用PHPLIB加入模板功能
    随机产生一个中文
    测试PHP
    获得指定后缀名的文件数
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/2686634.html
Copyright © 2011-2022 走看看