迷你MVVM框架 avalonjs 学习教程2二、avalon性能大揭密

avalon之因此能在页面处理1W个绑定(angular对应的数字是2000),出于两个重要设计——基于事件驱动的双向绑定链及智能CG回收机制。javascript

avalon的双向绑定链是经过Object.definePropertiesVBScript,将要操做VM属性变成一种访问器属性。访问器属性是一种特殊的属性,须要咱们为它指定setter、getter方法(固然,这也是框架内部生成的,只有计算属性能够作一些干预),当用户对此属性进行赋值操做时,就会调用setter方法,对它进行读取时,就会进行getter方法。咱们经过hack进这两个方法,作各类各样的事,如依赖收集及事件广播、触发$watch回调,其中最重要一点,就是将关联在它上面的订阅数组的对象逐个触发,从而更新视图,这也是双向绑定链的原理。好比说html

vm.me = 111

<div ms-text="me"></div>

me就会有一个订阅数组,里面放着一个对象,里面包含如此操做这个div的信息,若是有两个绑定属性的值存在me这个变量,这数组就有两个对象。触发是用户修改vm.me 时当即发生,不须要像angular那样调用$apply或$digest方法,也不像angular那样将全部绑定对象都检测一次。angular之因此会卡死,由于页面一旦绑定对象多,这检测时间也恐怖了,而且这检测多是深遍历对象的属性进行比较的。而avalon每次只会检测其一个属性上对应的小数组,所以检测压力会相对少量多。java

因为绑定属性会转换绑对象,而且绑定对象包含要操做的元素节点或文本特色这种惯例的存在,就会引起第二个问题,若是回收这些绑定对象呢?angular虽然为$scope对象添加了一个$destroy,但没有针对绑定对象有更精细的操做。avalon在1.36以前在notifySubscribers进行绑定对象的element进行是否在DOM树的检测,不在就将绑定对象的全部属性都置为null,并从当前订阅数组移除。node

avalon1.36/1.4引入全新的CG回收机制,页面上的{{}}插值表达式ms-*属性,通过扫描后,变成一个个绑定对象,对象包含name、value、type、param、vmodels、priority、args、vmodels、evaluator、handler等属性与方法,有些绑定对象还会多出template、 group、$repeat、proxies等属性(1.36前更多,如今都大幅精简了),所以绑定对象也算一个比较大的JS对象,页面上的绑定属性越多,这些绑定对象天然也越多,占用着大用的内存。若是页面发生一些移除节点操做,涉及这些绑定属性原来所在的元素节点,那么这些绑定对象也应该销毁,咱们就必须将binding.element 置为null,才会方便CG回收。以前是位于notifySubscribers方法里进行,但它处理的目标是一个很小的数组,不会检测全部绑定对象,所以总有漏网之鱼,这样积沙成塔,在移动端上就是一个很大的问题,会弄崩手机浏览器。git

在1.36/1.4中,内部定义一个全局的$$subscribers数组,全部生成的绑定对象都放到里面,而后每当我让VM的属性发生变化时,就必定会通过notifySubscribers方法,这时对$$subscribers数组的对象进行检测。而且这检测也有技巧,为了减小检测频率对浏览器形成压力,每次检测至少通过333ms才会进行一次。github

检测手段,是断定binding.element是否位于DOM树上,因为element多是元素节点,文本节点或注释节点,IE的原生contains方法也有BUG,因而检测也是多种多样的。算法

var $$subscribers = []
function removeSubscribers() {
    for (var i = $$subscribers.length, obj; obj = $$subscribers[--i]; ) {
        var data = obj.data
        var el = data.element
        var remove = el === null ? 1 : (el.nodeType === 1 ? typeof el.sourceIndex === "number" ?
                el.sourceIndex === 0 : !root.contains(el) : !avalon.contains(root, el))
        if (remove) { //若是它没有在DOM树
            $$subscribers.splice(i, 1)
            avalon.Array.remove(obj.list, data)
            // log("debug: remove " + data.type)
            obj.data = obj.list = data.evaluator = data.element = data.vmodels = null
        }
    }
}
var beginTime = new Date(), removeID
function notifySubscribers(accessor) { //通知依赖于这个访问器的订阅者更新自身
    var currentTime = new Date()
    clearTimeout(removeID)
    if (currentTime - beginTime > 333) {
        removeSubscribers()
        beginTime = currentTime
    } else {
        removeID = setTimeout(removeSubscribers, 333)
    }
    var list = accessor[subscribers]
    if (list && list.length) {
        var args = aslice.call(arguments, 1)
        for (var i = list.length, fn; fn = list[--i]; ) {
            var el = fn.element
            if (typeof fn === "function") {
                fn.apply(0, args) //强制从新计算自身
            } else if (fn.$repeat) {
                fn.handler.apply(fn, args) //处理监控数组的方法
            } else if (fn.element) {
                var fun = fn.evaluator || noop
                fn.handler(fun.apply(0, fn.args || []), el, fn)
            }
        }
    }
}

固然这是两个要点,为了提升性能,avalon也像一些大型应用程序那样,善于利用缓存机制。以前是使用FIFO缓存算法,1.4后改成LRU。数组

<!DOCTYPE html>
<html>
    <head>
        <title>监控函数</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="avalon.js"></script>
        <style>

            .odd{
                background: green;
            }
        </style>
        <script type="text/javascript">
            var model = avalon.define({
                $id: "test",
                array: [1, 2, 3, 4, 5],
                more: 10,
                isOdd: function(el) {
                    return (el + model.more) % 2 === 1
                },
                add: function() {
                    model.array.push(1)
                },
                remove: function() {
                    model.array.pop()
                },
                change: function(){
                    model.more = 11
                }

            })

</script>
</head>
<body ms-controller="test" >
    <ul>
        <li ms-repeat="array" ms-class="odd:isOdd(el)">{{el}}</li>
    </ul>
    <p><button ms-click="add">add</button><button ms-click="remove">remove</button><button ms-click="change">change</button></p>
</body>
</html>

enter image description here

相关文章
相关标签/搜索