迷你MVVM框架 avalonjs v0.5.1发布,性能大幅提高

早在avalon在IE与firefox有较为严重的性能问题,chrome等则由于它们太逆天因此看不出。主要原因是动态插入节点时,each由于一些帮方法考虑不周,结果不得不逐个插入,虽然使用了nextTick进行缓冲,但延迟明显。然后avalonjs v5把大体的架构完成了,然后重点改良这些方法了。在v5.1中所有延迟都没有了,即便在IE6下也很接近之前在chrome的运行效果。算是一次非常出色的改进。

1, addItemView方法不再逐个复制,逐个插入了。


//原来

 function addItemView(index, item, data) {

        var scopes = data.scopeList;

        var collection = data.collection;

        var parent = data.element;

        var doc = parent.ownerDocument;

        var textNodes = [];

        var scope = createItemModel(index, item, collection, data.args);

        scopes = [scope].concat(scopes)

        for (var node = data.view.firstChild; node; node = node.nextSibling) {

            var clone = node.cloneNode(true);

            if (collection.insertBefore) { //必须插入DOM树,否则下为注释节点添加自定义属性会失败

                parent.insertBefore(clone, collection.insertBefore);

            } else {

                parent.appendChild(clone);

            }

            if (clone.nodeType === 1) {

                scanTag(clone, scopes.concat(), doc); //扫描元素节点

            } else if (clone.nodeType === 3) {

                textNodes.push(clone); //插值表达式所在的文本节点会被移除,创建循环中断(node.nextSibling===null)

            } else if (clone.nodeType === 8) {

                clone.nodeValue = node.nodeValue + "" + index;

                if (!clone.addScope) {

                    clone.$scope = scope;

                    clone.addScope = "addItemView";

                }

                clone.$view = data.view.cloneNode(false);

            }

        }

        avalon.nextTick(function() {

            for (var i = 0; node = textNodes[i++]; ) {

                scanText(node, scopes.concat(), doc); //扫描文本节点

            }

        })

    }

改为


    function addItemView(index, item, data) {

        var scopes = data.scopes;

        var list = data.list;

        var parent = data.element;

        var doc = parent.ownerDocument;

        var scope = createItemModel(index, item, list, data.args);

        scopes = [scope].concat(scopes);

        var view = data.view.cloneNode(true);//★★★★

        var textNodes = [];

        var elements = [];

        for (var node = view.firstChild; node; node = node.nextSibling) {

            if (node.nodeType === 1) {

                elements.push(node);

            } else if (node.nodeType === 3) {

                textNodes.push(node);

            } else if (node.nodeType === 8) {

                node.id = node.nodeValue + index; //设置路标

                node.$scope = scope;

                node.$view = view.cloneNode(false);//★★★★

            }

        }

        // parent.insertBefore(el, null) === parent.appendChild(el)

        parent.insertBefore(view, list.place || null);



        for (var i = 0; node = elements[i++];) {

            scanTag(node, scopes.concat(), doc); //扫描文本节点

        }

        avalon.nextTick(function() {

            if (!parent.inprocess) {

                parent.inprocess = 1; //作用类似于display:none

                var hidden = parent.hidden; //http://html5accessibility.com/

                parent.hidden = true;//★★★★ 防止reflow

            }

            for (var i = 0; node = textNodes[i++];) {

                scanText(node, scopes.concat(), doc); //扫描文本节点

            }

            if (parent.inprocess) {

                parent.hidden = hidden;

                parent.inprocess = 0;

            }

        })

    }



2,新的路标系统:avalon使用一个注释节点来确认每个子模板的起点, 像emberjs则是使用两个script节点, knockout是使用两个注释节点 。




//原来

//路标是指每个模板最开头的那个注释节点

    //<!--xxx1--><tag><tag><text><!--xxx2--><tag><tag><text><!--xxx3--><tag><tag><text>

    // 假若 index == 2, 返回<!--xxx2-->



    function findIndex(elem, listName, index) {

        for (var node = elem.firstChild; node; node = node.nextSibling) {

            if (node.nodeType === 8 && (node.nodeValue === listName + index)) {

                return node;

            }

        }

    }



    //重置所有路标



    function resetIndex(elem, name) {

        var index = 0;

        for (var node = elem.firstChild; node; node = node.nextSibling) {

            if (node.nodeType === 8) {

                if (node.nodeValue.indexOf(name) === 0) {

                    if (node.nodeValue !== name + index) {

                        node.nodeValue = name + index;

                        var scope = node.$scope || {};

                        scope.$index = index;

                    }

                    index++;

                }

            }

        }

    }

现在的逻辑简化成这样:




    function findIndex(elem, index) { //寻找路标

        for (var node = elem.firstChild; node; node = node.nextSibling) {

            if (node.id === node.nodeValue + index) {//★★★★

                return node;

            }

        }

    }



    function resetIndex(elem, name) { //重置路标

        var index = 0;

        for (var node = elem.firstChild; node; node = node.nextSibling) {

            if (node.nodeType === 8 && node.nodeValue === name) {//★★★★

                if (node.id !== name + index) {

                    node.id = name + index;//★★★★

                    node.$scope.$index = index;

                }

                index++;

            }

        }

    }

3,移除模板的两个函数合并成一个,因此整体代码量都下降了。




//原来

    function removeItemView(node, listName) {

        var nodes = [node];

        var view = node.$view;

        for (var check = node.nextSibling; check; check = check.nextSibling) {

            //遇到下个路标时就断开

            if (check.nodeType === 8 && check.nodeValue.indexOf(listName) === 0) {

                break

            }

            nodes.push(check);

        }

        for (var i = 0; node = nodes[i++]; ) {

            view.appendChild(node);

        }

        return [view, check]; //返回被移除的文档碎片及下一个路标

    }

    //移除each中的多个子视图,返回它们对应的文档碎片集合



    function removeItemViews(node, listName, number) {

        var views = [];

        do {

            var array = removeItemView(node, listName);

            if (array[1]) {

                views.push(array[0]);

                node = array[1];

            } else {

                break

            }

        } while (views.length !== number);

        return views;

    }

现在是




    function emptyNode(parent) {//它直接用于clear与update方法

        while (parent.firstChild) {

            parent.removeChild(parent.firstChild);

        }

    }

    function removeItemView(node, id) { 

        var nodes = [node];

        var view = node.$view;

        for (var check = node.nextSibling; check; check = check.nextSibling) {

            if (check.nodeType === 8 && check.id === id) {

                break

            }

            nodes.push(check);

        }

        for (var i = 0; node = nodes[i++];) {

            view.appendChild(node);

        }

        emptyNode(view);//★★★★

        view = null;//★★★★

    }

可以到它的主页查看效果!如果有什么好的改进,记得pull request啊!

你可能感兴趣的:(val)