您的位置:首页 > Web前端

前端MVVM框架avalon - 模型转换1

2013-06-21 14:19 267 查看

轻量级前端MVVM框架avalon-模型转换(一)

接上一章ViewModel

modelFactory工厂是如何加工用户定义的VM?

附源码

[align=left]洋洋洒洒100多行内部是魔幻般的实现[/align]

functionmodelFactory(scope){


varskipArray=scope.$skipArray,//要忽略监控的属性名列表


model={},


Descriptions={},//内部用于转换的对象


json={},


callSetters=[],


callGetters=[],


VBPublics=Object.keys(watchOne);//用于IE6-8


skipArray=Array.isArray(skipArray)?skipArray.concat(VBPublics):VBPublics;


forEach(scope,function(name,value){


if(!watchOne[name]){


json[name]=value;


}


varvalueType=avalon.type(value);


if(valueType==="Function"){


VBPublics.push(name);//函数无需要转换


}else{


if(skipArray.indexOf(name)!==-1){


returnVBPublics.push(name);


}


if(name.charAt(0)==="$"&&!systemOne[name]){


returnVBPublics.push(name);


}


varaccessor,oldArgs;


if(valueType==="Object"&&typeofvalue.get==="function"&&Object.keys(value).length<=2){


varsetter=value.set,


getter=value.get;


accessor=function(neo){//创建计算属性


if(arguments.length){


if(stopRepeatAssign){


return;//阻止重复赋值


}


if(typeofsetter==="function"){


setter.call(model,neo);


}


if(oldArgs!==neo){//由于VBS对象不能用Object.prototype.toString来判定类型,我们就不做严密的检测


oldArgs=neo;


notifySubscribers(accessor);//通知顶层改变


model.$events&&model.$fire(name,neo,value);


}


}else{


if(openComputedCollect||!accessor.locked){


collectSubscribers(accessor);


}


returnvalue=json[name]=getter.call(model);//保存新值到json[name]


}


};


accessor.nick=name;


callGetters.push(accessor);


}else{


value=NaN;


callSetters.push(name);


accessor=function(neo){//创建监控属性或数组


if(arguments.length){


if(stopRepeatAssign){


return;//阻止重复赋值


}


if(value!==neo){


varold=value;


if(valueType==="Array"||valueType==="Object"){


if(value&&value.$id){


updateViewModel(value,neo,Array.isArray(neo));


}elseif(Array.isArray(neo)){


value=Collection(neo,model,name);


}else{


value=modelFactory(neo);


}


}else{


value=neo;


}


json[name]=value&&value.$id?value.$json:value;


notifySubscribers(accessor);//通知顶层改变


model.$events&&model.$fire(name,value,old);


}


}else{


collectSubscribers(accessor);//收集视图函数


returnvalue;


}


};


}


accessor[subscribers]=[];


Descriptions[name]={


set:accessor,


get:accessor,


enumerable:true


};


}


});


if(defineProperties){


defineProperties(model,Descriptions);


}else{


model=VBDefineProperties(Descriptions,VBPublics);


}


VBPublics.forEach(function(name){


if(!watchOne[name]){


model[name]=scope[name];


}


});


callSetters.forEach(function(prop){


model[prop]=scope[prop];//为空对象赋值


});


callGetters.forEach(function(fn){


Publish[expose]=fn;


callSetters=model[fn.nick];


fn.locked=1;


deletePublish[expose];


});


model.$json=json;


model.$events={};//VB对象的方法里的this并不指向自身,需要使用bind处理一下


model.$watch=Observable.$watch.bind(model);


model.$unwatch=Observable.$unwatch.bind(model);


model.$fire=Observable.$fire.bind(model);


model.$id=generateID();


model.hasOwnProperty=function(name){


returnnameinmodel.$json;


};


returnmodel;


}



VM是用ecma262v5的新API,Object.defineProperties生成的一个充满访问器的对象,这样的对象,能通过用户对它的属性的读写,触发定义时的getter,setter函数。getter,setter对rubyer,pythoner,C#er应该很熟悉,我就不展开了。

[align=left]旧式IE,avalon利用VBScript的类实例,它也存在其他语言的访问器。不过,VBS对象不像JS对象那样随意添加新属性,删除已有属性,因此我们就无法监后添加的新属性。Object.defineProperties也一样,它能处理的属性也只是它定义时的属性,想监控后来的,需要再调用一次Object.defineProperties。[/align]

整个工厂方法内部都是围绕着scope处理

过滤监控的属性

收集视图函数

转换用于定义

skipArray//要忽略监控的属性名列表

0:"$json"
1:"$skipArray"
2:"$watch"
3:"$unwatch"
4:"$fire"
5:"$events"


我们还是已官网的demo为列

avalon.define("simple",function(vm){
vm.firstName="司徒"
vm.lastName="正美"
vm.fullName={//一个包含set或get的对象会被当成PropertyDescriptor,
set:function(val){//set,get里面的this不能改成vm
vararray=(val||"").split("");
this.firstName=array[0]||"";
this.lastName=array[1]||"";
},
get:function(){
returnthis.firstName+""+this.lastName;
}
}
})
avalon.scan(document.querySelector("fieldset"));


此时传入的vm为

$watch:functionnoop(){
firstName:"司徒"
fullName:Object
lastName:"正美"


意图很明显就是遍历这些属性,给出相对应的处理,具体我们接着往下看

纯净的js对象,所有访问器与viewModel特有的方法属性都去掉

if(!watchOne[name]){

[code]json[name]=value;
}

[/code]
几个简单的条件过滤:

//判断类型

[code]varvalueType=avalon.type(value);

if(valueType==="Function"){

//第一个就是$watch"被重复假如到列表了

VBPublics.push(name);//函数无需要转换
}else{

[/code]

跳过过滤的条件后:

核心的转换

转换计算属性

转化监控属性

转换计算属性:

定义时为一个最多拥有get,set方法的对象(get方法是必需的)

注意,get,set里面的this不能改为vm,框架内部会帮你调整好指向。

判断的条件,值类型是对象,并且有get方法,并且方法要少于等于2个

if(valueType==="Object"&&typeofvalue.get==="function"&&Object.keys(value).length<=2){

满足条件的

vm.fullName={//一个包含set或get的对象会被当成PropertyDescriptor,
set:function(val){//set,get里面的this不能改成vm
vararray=(val||"").split("");
this.firstName=array[0]||"";
this.lastName=array[1]||"";
},
get:function(){
returnthis.firstName+""+this.lastName;
}
}

具体有什么用我们接着往下看

转化监控属性

定义时为一个简单的数据类型,如undefined,string,number,boolean。

监控数组:定义时为一个数组


firstName:"司徒"



accessor[subscribers]=[];


别看这个代码是空的函数,不起眼,双向绑定就是看他了,我们先Mark下

//生成defineProperties需要的配置属性
Descriptions[name]={
set:accessor,
get:accessor,
enumerable:true
};


Descriptions临时对象//收集内部用于转换的对象

enumerable很重要,为false的话,forin就找不到它了

这样循环后就把该干嘛的不该干嘛的都给区分开了

最后都保存在Descriptions中

此时的Descriptions


Descriptions:Object

[code]
firstName:Object

enumerable:true

get:function(neo){//创建监控属性或数组

set:function(neo){//创建监控属性或数组


fullName:Object

enumerable:true

get:function(neo){//创建计算属性

set:function(neo){//创建计算属性


lastName:Object

enumerable:true

get:function(neo){//创建监控属性或数组

set:function(neo){//创建监控属性或数组


[/code]
看吧就是这样给包装了一下,只是定义了但是还没生效

所以defineProperties(model,Descriptions);给执行以下(defineProperties的方法见前面)

model就是工厂模式转换后的新的vm模型对象了,因为在开始遍历scope的过滤了一些东东,原本也是用户定义的,所以这时候我们还得加到新的vm-》model中去、

//添加用户定义的未转换的函数到模型
VBPublics.forEach(function(name){
if(!watchOne[name]){
model[name]=scope[name];
}
});
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
章节导航