接上一章 ViewModel
?
modelFactory工廠是如何加工用戶定義的VM?
附源碼
- 洋洋灑灑100多行內(nèi)部是魔幻般的實(shí)現(xiàn)
1: function modelFactory(scope) {
2: var skipArray = scope.$skipArray,
//要忽略監(jiān)控的屬性名列表 3: model = {},
4: Descriptions = {},
//內(nèi)部用于轉(zhuǎn)換的對(duì)象 5: json = {},
6: callSetters = [],
7: callGetters = [],
8: VBPublics = Object.keys(watchOne);
//用于IE6-8 9: skipArray = Array.isArray(skipArray) ? skipArray.concat(VBPublics) : VBPublics;
10: forEach(scope,
function(name, value) {
11: if (!watchOne[name]) {
12: json[name] = value;
13: }
14: var valueType = avalon.type(value);
15: if (valueType ===
"Function") {
16: VBPublics.push(name);
//函數(shù)無需要轉(zhuǎn)換 17: }
else {
18: if (skipArray.indexOf(name) !== -1) {
19: return VBPublics.push(name);
20: }
21: if (name.charAt(0) ===
"$" && !systemOne[name]) {
22: return VBPublics.push(name);
23: }
24: var accessor, oldArgs;
25: if (valueType ===
"Object" &&
typeof value.get ===
"function" && Object.keys(value).length <= 2) {
26: var setter = value.set,
27: getter = value.get;
28: accessor =
function(neo) {
//創(chuàng)建計(jì)算屬性 29: if (arguments.length) {
30: if (stopRepeatAssign) {
31: return;
//阻止重復(fù)賦值 32: }
33: if (
typeof setter ===
"function") {
34: setter.call(model, neo);
35: }
36: if (oldArgs !== neo) {
//由于VBS對(duì)象不能用Object.prototype.toString來判定類型,我們就不做嚴(yán)密的檢測 37: oldArgs = neo;
38: notifySubscribers(accessor);
//通知頂層改變 39: model.$events && model.$fire(name, neo, value);
40: }
41: }
else {
42: if (openComputedCollect || !accessor.locked) {
43: collectSubscribers(accessor);
44: }
45: return value = json[name] = getter.call(model);
//保存新值到j(luò)son[name] 46: }
47: };
48: accessor.nick = name;
49: callGetters.push(accessor);
50: }
else {
51: value = NaN;
52: callSetters.push(name);
53: accessor =
function(neo) {
//創(chuàng)建監(jiān)控屬性或數(shù)組 54: if (arguments.length) {
55: if (stopRepeatAssign) {
56: return;
//阻止重復(fù)賦值 57: }
58: if (value !== neo) {
59: var old = value;
60: if (valueType ===
"Array" || valueType ===
"Object") {
61: if (value && value.$id) {
62: updateViewModel(value, neo, Array.isArray(neo));
63: }
else if (Array.isArray(neo)) {
64: value = Collection(neo, model, name);
65: }
else {
66: value = modelFactory(neo);
67: }
68: }
else {
69: value = neo;
70: }
71: json[name] = value && value.$id ? value.$json : value;
72: notifySubscribers(accessor);
//通知頂層改變 73: model.$events && model.$fire(name, value, old);
74: }
75: }
else {
76: collectSubscribers(accessor);
//收集視圖函數(shù) 77: return value;
78: }
79: };
80: }
81: accessor[subscribers] = [];
82: Descriptions[name] = {
83: set: accessor,
84: get: accessor,
85: enumerable:
true 86: };
87: }
88: });
89: if (defineProperties) {
90: defineProperties(model, Descriptions);
91: }
else {
92: model = VBDefineProperties(Descriptions, VBPublics);
93: }
94: VBPublics.forEach(
function(name) {
95: if (!watchOne[name]) {
96: model[name] = scope[name];
97: }
98: });
99: callSetters.forEach(
function(prop) {
100: model[prop] = scope[prop];
//為空對(duì)象賦值 101: });
102: callGetters.forEach(
function(fn) {
103: Publish[expose] = fn;
104: callSetters = model[fn.nick];
105: fn.locked = 1;
106: delete Publish[expose];
107: });
108: model.$json = json;
109: model.$events = {};
//VB對(duì)象的方法里的this并不指向自身,需要使用bind處理一下 110: model.$watch = Observable.$watch.bind(model);
111: model.$unwatch = Observable.$unwatch.bind(model);
112: model.$fire = Observable.$fire.bind(model);
113: model.$id = generateID();
114: model.hasOwnProperty =
function(name) {
115: return name
in model.$json;
116: };
117: return model;
118: }
- VM是用ecma262v5的新API, Object.defineProperties生成的一個(gè)充滿訪問器的對(duì)象,這樣的對(duì)象,能通過用戶對(duì)它的屬性的讀寫,觸發(fā)定義時(shí)的getter, setter函數(shù)。getter, setter對(duì)rubyer, pythoner, C#er應(yīng)該很熟悉,我就不展開了。
- 舊式IE,avalon利用VBScript的類實(shí)例,它也存在其他語言的訪問器。不過,VBS對(duì)象不像JS對(duì)象那樣隨意添加新屬性,刪除已有屬性,因此我們就無法監(jiān)后添加的新屬性。Object.defineProperties也一樣,它能處理的屬性也只是它定義時(shí)的屬性,想監(jiān)控后來的,需要再調(diào)用一次Object.defineProperties。
?
整個(gè)工廠方法內(nèi)部都是圍繞著scope處理
過濾監(jiān)控的屬性收集視圖函數(shù)轉(zhuǎn)換用于定義skipArray //要忽略監(jiān)控的屬性名列表
0:
"$json"
1:
"$skipArray"
2:
"$watch"
3:
"$unwatch"
4:
"$fire"
5:
"$events"
?
我們還是已官網(wǎng)的demo為列
avalon.define(
"simple",
function(vm) {vm.firstName =
"司徒"vm.lastName =
"正美"vm.fullName = {
//一個(gè)包含set或get的對(duì)象會(huì)被當(dāng)成PropertyDescriptor,set:
function(val) {
//set, get里面的this不能改成vmvar array = (val ||
"").split(
" ");
this.firstName = array[0] ||
"";
this.lastName = array[1] ||
"";},get:
function() {
return this.firstName +
" " +
this.lastName;}}})avalon.scan(document.querySelector(
"fieldset"));
?
此時(shí)傳入的vm為
$watch:
function noop() {firstName:
"司徒"fullName: ObjectlastName:
"正美" ?
意圖很明顯就是遍歷這些屬性,給出相對(duì)應(yīng)的處理,具體我們接著往下看
?????????? 純凈的js對(duì)象,所有訪問器與viewModel特有的方法屬性都去掉
1: if (!watchOne[name]) {
2: json[name] = value;
3: }
幾個(gè)簡單的條件過濾:
1: //判斷類型 2: var valueType = avalon.type(value);
3: ?
4: if (valueType ===
"Function") {
5: // 第一個(gè)就是$watch" 被重復(fù)假如到列表了 6: VBPublics.push(name);
//函數(shù)無需要轉(zhuǎn)換 7: }
else {
?
跳過過濾的條件后:
核心的轉(zhuǎn)換
- 轉(zhuǎn)換計(jì)算屬性
- 轉(zhuǎn)化監(jiān)控屬性
?
轉(zhuǎn)換計(jì)算屬性:
定義時(shí)為一個(gè)最多擁有g(shù)et,set方法的對(duì)象(get方法是必需的)注意,get, set里面的this不能改為vm,框架內(nèi)部會(huì)幫你調(diào)整好指向。判斷的條件,值類型是對(duì)象,并且有g(shù)et方法,并且方法要少于等于2個(gè)
if (valueType ===
"Object" &&
typeof value.get ===
"function" && Object.keys(value).length <= 2) {
滿足條件的
vm.fullName = {
//一個(gè)包含set或get的對(duì)象會(huì)被當(dāng)成PropertyDescriptor,set:
function(val) {
//set, get里面的this不能改成vmvar array = (val ||
"").split(
" ");
this.firstName = array[0] ||
"";
this.lastName = array[1] ||
"";},get:
function() {
return this.firstName +
" " +
this.lastName;}}
具體有什么用我們接著往下看
?
轉(zhuǎn)化監(jiān)控屬性
定義時(shí)為一個(gè)簡單的數(shù)據(jù)類型,如undefined, string, number, boolean。監(jiān)控?cái)?shù)組:定義時(shí)為一個(gè)數(shù)組 firstName:
"司徒"
?
?
accessor[subscribers] = [];
- 別看這個(gè)代碼是空的函數(shù),不起眼,雙向綁定就是看他了,我們先Mark下
//生成defineProperties需要的配置屬性Descriptions[name] = {set: accessor,get: accessor,enumerable:
true};
- Descriptions臨時(shí)對(duì)象? //收集內(nèi)部用于轉(zhuǎn)換的對(duì)象
- enumerable 很重要,為false的話 ,for in就找不到它了
這樣循環(huán)后就把該干嘛的不該干嘛的都給區(qū)分開了
最后都保存在Descriptions中
此時(shí)的Descriptions
1: Descriptions: Object
2: ?
3: firstName: Object
4: enumerable:
true 5: get:
function (neo) {
//創(chuàng)建監(jiān)控屬性或數(shù)組 6: set:
function (neo) {
//創(chuàng)建監(jiān)控屬性或數(shù)組 7: ?
8: fullName: Object
9: enumerable:
true 10: get:
function (neo) {
//創(chuàng)建計(jì)算屬性 11: set:
function (neo) {
//創(chuàng)建計(jì)算屬性 12: ?
13: lastName: Object
14: enumerable:
true 15: get:
function (neo) {
//創(chuàng)建監(jiān)控屬性或數(shù)組 16: set:
function (neo) {
//創(chuàng)建監(jiān)控屬性或數(shù)組 ?
看吧就是這樣給包裝了一下,只是定義了但是還沒生效
所以defineProperties(model, Descriptions); 給執(zhí)行以下? (defineProperties的方法見前面)
?
model 就是工廠模式轉(zhuǎn)換后的新的vm模型對(duì)象了, 因?yàn)樵陂_始遍歷scope的過濾了一些東東,原本也是用戶定義的,所以這時(shí)候我們還得加到新的vm-》model中去、
//添加用戶定義的未轉(zhuǎn)換的函數(shù)到模型VBPublics.forEach(
function(name) {
if (!watchOne[name]) {model[name] = scope[name];}});
轉(zhuǎn)載于:https://www.cnblogs.com/aaronjs/p/3146848.html
總結(jié)
以上是生活随笔為你收集整理的轻量级前端MVVM框架avalon - 模型转换的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。