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}