forked from RubyLouvre/avalon
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path07 modelFactory.js
335 lines (321 loc) · 11.9 KB
/
07 modelFactory.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/*********************************************************************
* modelFactory *
**********************************************************************/
//avalon最核心的方法的两个方法之一(另一个是avalon.scan),返回一个ViewModel(VM)
var VMODELS = avalon.vmodels = {} //所有vmodel都储存在这里
avalon.define = function(id, factory) {
var $id = id.$id || id
if (!$id) {
log("warning: vm必须指定$id")
}
if (VMODELS[$id]) {
log("warning: " + $id + " 已经存在于avalon.vmodels中")
}
if (typeof id === "object") {
var model = modelFactory(id)
} else {
var scope = {
$watch: noop
}
factory(scope) //得到所有定义
model = modelFactory(scope) //偷天换日,将scope换为model
stopRepeatAssign = true
factory(model)
stopRepeatAssign = false
}
model.$id = $id
return VMODELS[$id] = model
}
//一些不需要被监听的属性
var $$skipArray = String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray").match(rword)
function isObservable(name, value, $skipArray) {
if (isFunction(value) || value && value.nodeType) {
return false
}
if ($skipArray.indexOf(name) !== -1) {
return false
}
if ($$skipArray.indexOf(name) !== -1) {
return false
}
var $special = $skipArray.$special
if (name && name.charAt(0) === "$" && !$special[name]) {
return false
}
return true
}
//ms-with,ms-each, ms-repeat绑定生成的代理对象储存池
var midway = {}
function getNewValue(accessor, name, value, $vmodel) {
switch (accessor.type) {
case 0://计算属性
var getter = accessor.get
var setter = accessor.set
if (isFunction(setter)) {
var $events = $vmodel.$events
var lock = $events[name]
$events[name] = [] //清空回调,防止内部冒泡而触发多次$fire
setter.call($vmodel, value)
$events[name] = lock
}
return getter.call($vmodel) //同步$model
case 1://监控属性
return value
case 2://对象属性(包括数组与哈希)
if (value !== $vmodel.$model[name]) {
var svmodel = accessor.svmodel = objectFactory($vmodel, name, value, accessor.valueType)
value = svmodel.$model //同步$model
var fn = midway[svmodel.$id]
fn && fn() //同步视图
}
return value
}
}
var defineProperty = Object.defineProperty
var canHideOwn = true
//如果浏览器不支持ecma262v5的Object.defineProperties或者存在BUG,比如IE8
//标准浏览器使用__defineGetter__, __defineSetter__实现
try {
defineProperty({}, "_", {
value: "x"
})
var defineProperties = Object.defineProperties
} catch (e) {
canHideOwn = false
}
function modelFactory(source, $special, $model) {
if (Array.isArray(source)) {
var arr = source.concat()
source.length = 0
var collection = Collection(source)
collection.pushArray(arr)
return collection
}
if (typeof source.nodeType === "number") {
return source
}
if (source.$id && source.$events) { //fix IE6-8 createWithProxy $val: val引发的BUG
return source
}
if (!Array.isArray(source.$skipArray)) {
source.$skipArray = []
}
source.$skipArray.$special = $special || {} //强制要监听的属性
var $vmodel = {} //要返回的对象, 它在IE6-8下可能被偷龙转凤
$model = $model || {} //vmodels.$model属性
var $events = {} //vmodel.$events属性
var watchedProperties = {} //监控属性
var initCallbacks = [] //初始化才执行的函数
for (var i in source) {
(function(name, val) {
$model[name] = val
if (!isObservable(name, val, source.$skipArray)) {
return //过滤所有非监控属性
}
//总共产生三种accessor
$events[name] = []
var valueType = avalon.type(val)
var accessor = function(newValue) {
var name = accessor._name
var $vmodel = this
var $model = $vmodel.$model
var oldValue = $model[name]
var $events = $vmodel.$events
if (arguments.length) {
if (stopRepeatAssign) {
return
}
//计算属性与对象属性需要重新计算newValue
if (accessor.type !== 1) {
newValue = getNewValue(accessor, name, newValue, $vmodel)
if (!accessor.type)
return
}
if (!isEqual(oldValue, newValue)) {
$model[name] = newValue
if ($events.$digest) {
if (!accessor.pedding) {
accessor.pedding = true
setTimeout(function() {
notifySubscribers($events[name]) //同步视图
safeFire($vmodel, name, $model[name], oldValue) //触发$watch回调
accessor.pedding = false
})
}
} else {
notifySubscribers($events[name]) //同步视图
safeFire($vmodel, name, newValue, oldValue) //触发$watch回调
}
}
} else {
if (accessor.type === 0) { //type 0 计算属性 1 监控属性 2 对象属性
//计算属性不需要收集视图刷新函数,都是由其他监控属性代劳
newValue = accessor.get.call($vmodel)
if (oldValue !== newValue) {
$model[name] = newValue
//这里不用同步视图
if ($events.$digest) {
if (!accessor.pedding) {
accessor.pedding = true
setTimeout(function() {
safeFire($vmodel, name, $model[name], oldValue) //触发$watch回调
accessor.pedding = false
})
}
} else {
safeFire($vmodel, name, newValue, oldValue) //触发$watch回调
}
}
return newValue
} else {
collectSubscribers($events[name]) //收集视图函数
return accessor.svmodel || oldValue
}
}
}
//总共产生三种accessor
if (valueType === "object" && isFunction(val.get) && Object.keys(val).length <= 2) {
//第1种为计算属性, 因变量,通过其他监控属性触发其改变
accessor.set = val.set
accessor.get = val.get
accessor.type = 0
initCallbacks.push(function() {
var data = {
evaluator: function() {
data.type = Math.random(),
data.element = null
$model[name] = accessor.get.call($vmodel)
},
element: head,
type: Math.random(),
handler: noop,
args: []
}
Registry[expose] = data
accessor.call($vmodel)
delete Registry[expose]
})
} else if (rcomplexType.test(valueType)) {
//第2种为对象属性,产生子VM与监控数组
accessor.type = 2
accessor.valueType = valueType
initCallbacks.push(function() {
var svmodel = modelFactory(val, 0, $model[name])
accessor.svmodel = svmodel
svmodel.$events[subscribers] = $events[name]
})
} else {
accessor.type = 1
//第3种为监控属性,对应简单的数据类型,自变量
}
accessor._name = name
watchedProperties[name] = accessor
})(i, source[i])
}
$$skipArray.forEach(function(name) {
delete source[name]
delete $model[name] //这些特殊属性不应该在$model中出现
})
$vmodel = defineProperties($vmodel, descriptorFactory(watchedProperties), source) //生成一个空的ViewModel
for (var name in source) {
if (!watchedProperties[name]) {
$vmodel[name] = source[name]
}
}
//添加$id, $model, $events, $watch, $unwatch, $fire
$vmodel.$id = generateID()
$vmodel.$model = $model
$vmodel.$events = $events
for ( i in EventBus) {
var fn = EventBus[i]
if (!W3C) { //在IE6-8下,VB对象的方法里的this并不指向自身,需要用bind处理一下
fn = fn.bind($vmodel)
}
$vmodel[i] = fn
}
if (canHideOwn) {
Object.defineProperty($vmodel, "hasOwnProperty", {
value: function(name) {
return name in this.$model
},
writable: false,
enumerable: false,
configurable: true
})
} else {
$vmodel.hasOwnProperty = function(name) {
return name in $vmodel.$model
}
}
initCallbacks.forEach(function(cb) { //收集依赖
cb()
})
return $vmodel
}
//比较两个值是否相等
var isEqual = Object.is || function(v1, v2) {
if (v1 === 0 && v2 === 0) {
return 1 / v1 === 1 / v2
} else if (v1 !== v1) {
return v2 !== v2
} else {
return v1 === v2
}
}
function safeFire(a, b, c, d) {
if (a.$events) {
EventBus.$fire.call(a, b, c, d)
}
}
var descriptorFactory = W3C ? function(obj) {
var descriptors = {}
for (var i in obj) {
descriptors[i] = {
get: obj[i],
set: obj[i],
enumerable: true,
configurable: true
}
}
return descriptors
} : function(a) {
return a
}
//应用于第2种accessor
function objectFactory(parent, name, value, valueType) {
//a为原来的VM, b为新数组或新对象
var son = parent[name]
if (valueType === "array") {
if (!Array.isArray(value) || son === value) {
return son //fix https://github.com/RubyLouvre/avalon/issues/261
}
son._.$unwatch()
son.clear()
son._.$watch()
son.pushArray(value.concat())
return son
} else {
var iterators = parent.$events[name]
var pool = son.$events.$withProxyPool
if (pool) {
recycleProxies(pool, "with")
son.$events.$withProxyPool = null
}
var ret = modelFactory(value)
ret.$events[subscribers] = iterators
midway[ret.$id] = function(data) {
while (data = iterators.shift()) {
(function(el) {
avalon.nextTick(function() {
if (el.type) { //重新绑定
el.rollback && el.rollback() //还原 ms-with ms-on
bindingHandlers[el.type](el, el.vmodels)
}
})
})(data)
}
delete midway[ret.$id]
}
return ret
}
}