视图模型

视图模型,ViewModel,也经常被略写成VM,是通过avalon.define方法进行定义,利用Object.defineProperties(IE9+及W3C)与VBScript(IE6-8)技术生成的特殊对象。 用户定义的VM都会放在avalon.vmodels对象中集中存储,因此VM必须指定$id属性,我们将通地var vm = avalon.vmodels[$id]来获取你的VM。 此外,VM还拥有 $watch, $unwatch, $fire, $model等方法。

none

视图里面,我们可以使用ms-controller, ms-important指定一个VM的作用域。

此外,在ms-each, ms-with,ms-repeat绑定属性中,它们会创建一个临时的VM,我们称之为代理VM, 用于放置$key, $val, $index, $last, $first, $remove等变量或方法。

另外,avalon不允许在VM定义之后,再追加新属性与方法,比如下面的方式是错误的:

var vm = avalon.define({
    $id: "test",
    test1: "点击测试按钮没反应 绑定失败"
})
vm.one = function() { //不能再追加此方法
    vm.test1 = "绑定成功"
}

但我们可以通过以下方式,实现添加子属性。

var vm = avalon.define({
    $id: "test",
    placehoder: {}
});
setTimeout(function() {
    vm.placehoder = { //我们必须要通过 = ,直接添加一个对象来添加子属性, 不能
        aaa: 1, //vm.placehoder.aaa =1; vm.placehoder.bbb = 2这样分散地添加子属性
        bbb: 2
    }
}, 1000)

VM中的数据更新,只能通过 = 赋值方式实现。但要注意在IE6-8,由于VM是一个VBScript对象,为VM添加新属性会抛错, 因此我们想批量更新属性要时格外小心了,需要用hasOwnProperty进行过滤。

注意在IE6-8 下,err是VBscript的关键字,VM中存在这个字段,就会将VM中的其他数组变成字符串,详见 这里

var vm = avalon.define({
    $id: "test",
    a: 1,
    b: 2,
    c: {}
});
var newObject = {
    a: 5,
    b: 6,
    c: {
        k: 4
    },
    f: 9 //f原来是在vm中不存在,在IE6-8会报错
}

//方式1:IE9+及其他现代浏览器
avalon.mix(vm, newObject)

//方式2:IE6-8
for (var i in vm) {
    if (vm.hasOwnProperty(i) && newObject.hasOwnProperty(i)) {
        vm[i] = newObject[i]
    }
}
//方式3, 设计一个assignVM方法,方便mixin N个对象
function assignVM(vm, firstSource) {
    for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource && typeof nextSource !== "object")
            continue;
        for (var i in vm) {
            if (vm.hasOwnProperty(i) && nextSource.hasOwnProperty(i)) {
                vm[i] = nextSource[i]
            }
        }
    }
    return vm
}

assignVM(vm, newObject, {
    a: 8,
    h: 9
}, {
    b: 6,
    j: 0
})

为了性能起见,请确保你的对象结构足够扁平,套嵌层次不能太深,里面的数组不能太长。

新旧风格

avalon在1.3引入新风格. 旧风格是指定义VM时需传入ID与回调, 新风格是指定义VM时需传入一个对象

//旧风格
avalon.define("test", function(vm) {
        vm.aaa = 1
        vm.bbb = "2"
    })
    //新风格
avalon.define({
    $id: "test",
    aaa: 1,
    bbb: "2"
})

这样就可以避开旧风格的三大陷阱

var array = []
var vmodel = avalon.define(id, function(vm) { //1你要区分vm与vmodel的区别,vm只能用于定义属性,vmodel只能更新里面的属性或执行它的方法
    vm.aa = 1
    array.push(10) //2这里会执行两次,你会发现array = [10,10]
    avalon.mix(vm, {
        aa: 2, //3这里在IE6-8下会报重复定义错误
        cc: 3
    })
    vm.bb = 3
    vm.$watch("bb", callback)
})

强烈建议大家都尽快升级到最新版,使用新风格定义VM。本教程从一开始也是使用新风格进行教学的!

VM能监控它的属性被用户改动的秘密

我们定义VM时,是通过define方法,无论是新风格还是旧风格,你最初添加的属性从这方法出来,已经不是原来的属性了。 这些能监控的属性在JS里有个一专门的术语叫访问器属性

javascript一共用三种属性

普通属性, 这种属性是用户赋给它们,它们就返回什么,不会做额外的事情

var a = {}
a.b = 1
alert(a.b) // 1

内部属性,比如数组的length属性,函数的prototype属性, DOM节点的innerHTML属性,用户对它们进行赋值后, 再取值时,它不一定按我们的预期做事,此外还会做一些格外的事情。另外,我们也很难改变它们的行为。 比如说某一数组,它的长度为10, 当我们设置它为11时,它就会增加一个undefined元素,再设置为9时,就会从后面删掉两个元素。 函数的prototype如果被改变,相当于将其父类改变了,会new不同类型的实例。 DOM的innerHTML,我们赋值时是一个字符串,再取出时,这字符串可能会与原来的不一样, 并且在原元素上生成了不一样的子节点。

var a = {}
a.b = 1
alert(a.b) // 1

访问器属性, IE8+新添加的语言特征,允许用户在赋值或取值都经过预先设定的函数,从而实现内部属性 的那一种特殊效果。比如说我们能让一个属性无法赋值,取值时都返回1000;让一个属性在赋值时,会执行另一处的方法……

现在javascript有三种方式设置访问器属性

HTMLElement.prototype.__defineGetter__("description", function() {
    return this.desc
})
HTMLElement.prototype.__defineSetter__("description", function(val) {
    this.desc = val
})
document.body.description = "Beautiful body"

function Lost() {
    // Constructor
}

Lost.prototype = {
    get location() {
        return this.loc;
    },
    set location(val) {
        this.loc = val;
    }
};
var lostIsland = new Lost();
lostIsland.location = "Somewhere in time";

var bValue = 38;
Object.defineProperty(o, 'b', {
    get: function() {
        return bValue;
    },
    set: function(newValue) {
        bValue = newValue;
    },
    enumerable: true,
    configurable: true
});
o.b; // 38

这三种定义方式,无法哪一种,都要求我们设置一个读方法getter,一个写方法setter。 getter, setter是用于改写用户访问某属性的行为。

一般来说,我们对某属性的常用操作有如下四种,赋值,取值,遍历,删除。赋值会其内部的set方法, 取值会调用其内部的get方法, 遍历关系到其enumerable配置项,删除关系到其configurable配置项。一般地,我们称那些用户赋什么值返回什么值的属性为普通属性, 在set, get关键字,或__defineSetter__, __defineGetter__没出来之前,大多数对象的属性都是普通属性。 只有像数组的length属性与元素节点的innerHTML, 会在用户取值或赋值做一些额外的操作,它们就是访问器属性。 avalon.define生成的VM就是一个包含了是访问器属性的魔术对象。 avalon会在它们的setter,getter方法做依赖收集与同步视图等工作, 从来让我们就算不写一行有关DOM操作的代码,也能做jQuery那种灵活操作DOM的效果。

值得注意的是,avalon只会转换预先定义好的属性为访问器属性,对后来添加的属性无动于衷。 因此大家要养成“先定义后使用”的习惯。

注意,IE6-8是使用VBScript生成的对象,是一种特殊的对象, 如果访问没有定义过的属性或方法会报错。 这个千万要小心。