“计算机之子”的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框架源码学习笔记的详细内容...