随着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]; }); }); }; } |
单从现在放出的源码,它的功能还是比较薄弱,期待更完整的版本吧!