好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

“计算机之子”的MVVM框架源码学习笔记

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

             });

         });

     };

}

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

 

 

标签:  mvvm

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于“计算机之子”的MVVM框架源码学习笔记的详细内容...

  阅读:45次