avalon2.2使用VBScript, Object.defineProperty, Proxy三种方式实现VM。现在市面上都是用Object.defineProperty。
但是光是Object.defineProperty是不够用,这里面涉及许多机制才能实现视图与数据的双向绑定。
比如下面的这个VM。
var vm = avalon.define({
$id: "xxx",
$ddd: 111,
a: "可监控",
$computed: {b: function(){return this.a+"!"} },
c: new Date,
f1: function(){},
d: {
e:1,
f2:function(){}
}
})
avalon.define要求传入一个带$id的对象。$id是必须的,没有会报错。
avalon.define的后面是modelFactory。它会遍历这个对象,从中筛选出所有可以监控的属性。
可以监控的属性,要求不能为函数,日期,正则,BOM,DOM,window这些特殊数据类型,如果你的类型为null, undefined,虽然也能转换,但会有警告。
此外,如果你的属性名是以$
开头,$
开头的属性是留给框架用的。
这些监控属性及位于$computed
的计算属性,都会生成对应的监控对象,放在$mutations
对象上。
遍历一遍,我们会将第一层的函数提出来,进行bind(vm)处理,这样可以解决IE6-8的差异化问题。至于子对象的函数,我们就不会处理了。
我们看chrome的控制台中的vm对象,比如aaa,它的值是末知的,只有点击它,它才会勿勿从get aaa
方法中计算出来,如果对它进行赋值 vm.aaa = 88
, 则需要经过set aaa
方法处理。这种属性叫访问器属性
,Object.defineProperty就是用来创建访问器属性的。可以说,这是我们实现MVVM的基石(在Proxy没出来之前)。
vm中有一个$mutations
对象,里面存放着aaa的监控对象。当然这些可以不暴露出来,像vue,则称之为Depend,放在闭包内。
$mutations
内部的样子,每个访问器属性会对应的Mutation实例。Mutation是惰性创建的。
function createAccessor(key, val, isComputed) {
var mutation = null
var Accessor = isComputed ? Computed : Mutation
return {
get: function Getter() {
if (!mutation) {
mutation = new Accessor(key, val, this)
}
return mutation.get()
},
set: function Setter(newValue) {
if (!mutation) {
mutation = new Accessor(key, val, this)
}
mutation.set(newValue)
},
enumerable: true,
configurable: true
}
}
比如我们为vm添加一个访问器属性,可以简化成这样的流程:
var accessor = createAccessor("aaa",111)
Object.defineProperty(vm, "aaa", accessor)
mutation实例有几个方法,当用户调用var a = vm.aaa
,相当于调用vm.$muations.aaa.get()
方法。我们会在属性取值时进行依赖收集,于是get里面会调用collect方法。如果赋值,则会调用它的set方法,从而调用它的notify方法与updateVerstion方法。
当一个vm创建完时,就会调用afterCreate方法,在这个方法中,我们会为vm添加$watch
, $fire
, $model
等成员,在IE6-8中还会添加hasOwnProperty方法。
下面是modelFactory的完整代码,可以看到所有VM都是IProxy的实例
platform.modelFactory = function modelFactory(definition, dd) {
var $computed = definition.$computed || {}
delete definition.$computed
var core = new IProxy(definition, dd)
var $accessors = core.$accessors
var keys = []
platform.hideProperty(core, '$mutations', {})
for (let key in definition) {
if (key in $$skipArray)
continue
var val = definition[key]
keys.push(key)
if (canHijack(key, val)) {
$accessors[key] = createAccessor(key, val)
}
}
for (let key in $computed) {
if (key in $$skipArray)
continue
var val = $computed[key]
if (typeof val === 'function') {
val = {
get: val
}
}
if (val && val.get) {
val.getter = val.get
val.setter = val.set
avalon.Array.ensure(keys, key)
$accessors[key] = createAccessor(key, val, true)
}
}
//将系统API以unenumerable形式加入vm,
//添加用户的其他不可监听属性或方法
//重写$track
//并在IE6-8中增添加不存在的hasOwnPropert方法
var vm = platform.createViewModel(core, $accessors, core)
platform.afterCreate(vm, core, keys, !dd)
return vm
}