4

avalon2.2使用VBScript, Object.defineProperty, Proxy三种方式实现VM。现在市面上都是用Object.defineProperty。

clipboard.png

但是光是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的差异化问题。至于子对象的函数,我们就不会处理了。

clipboard.png

我们看chrome的控制台中的vm对象,比如aaa,它的值是末知的,只有点击它,它才会勿勿从get aaa 方法中计算出来,如果对它进行赋值 vm.aaa = 88, 则需要经过set aaa方法处理。这种属性叫访问器属性,Object.defineProperty就是用来创建访问器属性的。可以说,这是我们实现MVVM的基石(在Proxy没出来之前)。

vm中有一个$mutations对象,里面存放着aaa的监控对象。当然这些可以不暴露出来,像vue,则称之为Depend,放在闭包内。

clipboard.png

$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)

clipboard.png

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
}


司徒正美
5.6k 声望3.5k 粉丝

穿梭于二次元与二进制间的魔法师( ̄(工) ̄) 凸ส้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้


« 上一篇
es6的一些细节
下一篇 »
avalon2.2.3发布

引用和评论

0 条评论