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

迷你MVVM框架 avalonjs v5.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啊!

 
 
 
标签:  javascript

你可能感兴趣的:(JavaScript)