“计算机之子”的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});width: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];

            });

        });

    };

}

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

你可能感兴趣的:(学习笔记)