资讯专栏INFORMATION COLUMN

avalon的ViewModel设计

sourcenode / 2013人阅读

摘要:在等库中,开头的属性方法都是框架自用的。使用或者更新的,就能得到所有访问器属性的定义对象,然后合成。至此,内部各种概念的关系图如下框架设计第三版,敬请期待

不是有了Object.defineProperty, Proxy或Reflect,放进一个对象就new出一个ViewModel出来。只能说,它们是必要条件。我们需要将要监听的属性变成访问器属性,所有访问器属性都是共用同一套setter, getter方法。getter里面做依赖收集(不是必须的),setter里做视图刷新或触发该属性的$watch回调。在此之前,我们需要完成一套观察者模式,就是github中常见的EventEmitter库:

https://github.com/Olical/Eve...

https://github.com/asyncly/Ev...

https://github.com/primus/eve...

但这些库的订阅数组都是放函数。我们需要放绑定对象,需要改造一下,并且改成$watch, $fire接口。

var EventBus = {
    $watch: function (type, callback) {
        var binding = callback
        if (typeof callback === "function") {
            binding = {
                expr: type,
                update: callback
            }
        }
        var bus = this.$events
        var list = bus[type]
        if (!list) {
            list = bus[type] = []
        }
        function unwatch() {
            avalon.Array.remove(list, binding)
            if(!list.length){
               delete bus[type]
            }
        }
        list.push(binding)
        return unwatch
    },
    $fire: function (type, value) {
        var list = this.$events[type]
        if (list && lsit.length) {
            for (var i = 0, obj; obj = list[i++];) {
                obj.update()
            }
        }
    }
}

然后我们再为它添加一个$id,用于标记这个VM是作用于页面某个元素上的。

var vm = avalon.define({
    $id: "test",
    aaa: 1,
    bbb: 2
})

{{@aaa}}

我们看avalon.define的一个简单实现:

avalon.define = function(obj) {
    var vm = {}
    var other = {}
    for (var name in obj) {
        if (typeof obj[name] !== "function" && name.charAt(0) !== "$") {
            (function (key, value) {
                function get(){
//在avalon1.4,1.5中这里会进行动态依赖收集,详见这里
//https://github.com/RubyLouvre/avalon/blob/1.5/src/10%20dependency.js                
                    return get._value
                }
                get._value = value
                Object.defineProperty(obj, key, {
                    set: function (newValue) {
                        if(newValue !== get._value){
                            get._value = newValue
                            if(vm.$hashcode)
                               vm.$fire(key, newValue)
                        }
                        return newValue
                    },
                    get: get
                })
            })(name, obj[name])
        }else{
            other[name] = obj[name]
        }
    }
    for(var name in other){
        vm[name] = other
    }
    vm.$events = {}
    vm.$hashcode = new Date  - Math.random()
    vm.$fire = EventBus.$fire
    vm.$watch = EventBus.$watch
   
    return avalon.vmodels[vm.$id] = vm
}

此外,你可以添加更多以$开头的属性方法,来增强它的功能。在avalon, angular等库中,$开头的属性方法都是框架自用的。avalon2的一个简单的vm是藏了许多不可遍历的$xxx属性方法:

那么如何将绑定属性放进vm.$events.aaa数组中呢。这就要靠扫描机制。从上到下扫描。

avalon.scan = function (el, vm) {
    scanNodes([el], vm)
}
function scanNodes(array, vm) {
    for (var i = 0, el; el = array[i++]; ) {
        switch (el.nodeType) {
            case 1:
                scanTag(el, vm)
                break
            case 3:
                scanText(el, vm)
                break
        }
    }
}

function scanTag(el, vm){
    var id = el.getAttribute("ms-controller")
    if(id && avalon.vmodels[id]){
        var vm2 = avalon.vmodels[id]
        if(vm && vm2 && vm == vm2){
           vm = mergeVM(vm,vm2)
        }else{
           vm = vm2
        }
        el.removeAttribute("ms-controller")
    }
    var bindings = scanAttrs(el,vm)
    for(var i = 0, b; b = bindings[i++];){
        vm.$watch(b.expr, b) //重点
    }
    if(el.children && el.children.length){
        scanNodes(el.children, vm)
    }
}
function scanText(){
    // 用正则检测是否有花括号
    // 有则转换为绑定对象
    // 并进行vm.$watch
}
function scanAttrs(){
    //遍历el.attributes中所有对象,看name是否以ms-开头
}

那么如何将绑定属性放进vm.$events.aaa数组中呢。这就要靠扫描机制。从上到下扫描。

avalon.scan = function (el, vm) {
    scanNodes([el], vm)
}
function scanNodes(array, vm) {
    for (var i = 0, el; el = array[i++]; ) {
        switch (el.nodeType) {
            case 1:
                scanTag(el, vm)
                break
            case 3:
                scanText(el, vm)
                break
        }
    }
}

function scanTag(el, vm){
    var id = el.getAttribute("ms-controller")
    if(id && avalon.vmodels[id]){
        var vm2 = avalon.vmodels[id]
        if(vm && vm2 && vm == vm2){
           vm = mergeVM(vm,vm2)
        }else{
           vm = vm2
        }
        el.removeAttribute("ms-controller")
    }
    var bindings = scanAttrs(el,vm)
    for(var i = 0, b; b = bindings[i++];){
        vm.$watch(b.expr, b) //重点
    }
    if(el.children && el.children.length){
        scanNodes(el.children, vm)
    }
}
function scanText(){
    // 用正则检测是否有花括号
    // 有则转换为绑定对象
    // 并进行vm.$watch
}
function scanAttrs(){
    //遍历el.attributes中所有对象,看name是否以ms-开头
}

里面用到一个mergeVM方法,其实也简单,就是将两个VM合并成一个新的VM。使用Object.getOwnPropertyDescriptor或者更新的Object.getOwnPropertyDescriptors,就能得到所有访问器属性的定义对象,然后合成。如果是古老浏览器,我们可以将访问器属性放到一个叫$accessors对象上。

现在我们这个VM是很简单的,它只支持一重属性,如果属性的属性也是对象呢。这个我们需要将这define方法递 归一下不就行了吗?!对于数组的监控,业界流行的方法是重写数组的大部分方法,然后再加上一些移除数组的方法。

至此,avalon内部各种概念的关系图如下:

from 《javascript框架设计》第三版,敬请期待

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87935.html

相关文章

  • angularViewModel设计

    摘要:换言之,的对应的,此外它还有。它们共同构成的监控系统。和是相辅相成的。两者一起,构成了作用域的核心功能数据变化的响应。迭代的最大值称为。框架设计第三版,敬请期待 angular的ViewModel有一个专门的官方术语叫$scope, 它只是一个普通构造器(Scope)的实例。换言之,它是一个普通的JS对象。为了实现MVVM框架通常宣传的那种改变数据即改变视图的魔幻效果,它得装备上更多更...

    int64 评论0 收藏0
  • WEB前端面试题整理(二)

    摘要:栈是操作系统在建立某个进程时或者线程为这个线程建立的存储区域。在具有多线程机制的操作系统中,处理机调度的基本单位不是进程而是线程。一个进程可以有多个线程,而且至少有一个可执行线程。 WEB前端面试题的记录(二) 1、一次完整的HTTP事务是怎样的一个过程:基本流程: 域名解析 发起TCP的3次握手 建立TCP连接后发起http请求 服务器端响应http请求,浏览器得到html代码 浏...

    solocoder 评论0 收藏0
  • 吐槽专用

    摘要:最终选择了兼容到的,终于使用上框架,虽然它只是个。没有对比就没有伤害本来想着技术栈统一,移动端也准备使用。于是,之后对移动端的技术选型上更加慎重了,最终采用了文档更漂亮的。易用还真不易用,坑还真多。 吐槽 avalon.js 历史背景 需求重大调整,所有业务推倒重来(pc端主要任务涉及管理后台类型的网站); 开发周期很紧,过年前要上线; 公司pc端业务要求兼容到ie8; 2015年前...

    zxhaaa 评论0 收藏0
  • avalon与masonry结合

    摘要:相关组件版本最近,在公司的项目中,要开发一个使用瀑布流的前台,衡量了各种解决方案后,还是觉得最成熟,所以就选用了它。测试的结果很令人沮丧,依然没有控制节点的位置,所以应该不是这个问题。 相关组件版本:avalon 1.3.6、masonry 3.1.5 最近,在公司的项目中,要开发一个使用瀑布流的前台,衡量了各种解决方案后,还是觉得masonry最成熟,所以就选用了它。而在之前开发后台...

    Kosmos 评论0 收藏0
  • 一步步编写avalon组件05:树组件

    摘要:给人印象中,树组件是非常令人畏惧的一个组件,超级复杂,超级难写。但使用来做,这却是级其简单的一件事。换言之,我们用元素作为树的节点,那么树组件内部也需要存在树组件,需要形成递归结构。的机制又是出场的时候了。 给人印象中,树组件是非常令人畏惧的一个组件,超级复杂,超级难写。但使用avalon2来做,这却是级其简单的一件事。首先从样式入做,无序列表是天然可用的树结构,几个UL元素套在一起,...

    Ocean 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<