每日一博 | 各种动态渲染 Element 方式的性能探究

栏目: 编程语言 · XML · 发布时间: 7年前

内容简介:每日一博 | 各种动态渲染 Element 方式的性能探究

###一、性能优化的原则及方法论

树立原则:动态渲染进入一个Dom元素,首先需要保证动态渲染操作必须尽可能少对原有dom树的影响,影响重绘及重排。

确定方法论:必须寻找一个 容器 来缓存渲染期间生成的dom结构(操作必须尽可能少对原有dom树的影响),然后再进行一次渲染到目标element中。

二、生成期间DOM缓存的选择

  • DocumentFragment(文档碎片对象,选择原因:脱离于文档流)
  • 临时Element(选择原因:新element脱离于文档流)
    • createElement,再一步步进行渲染
    • 通过描述Dom的String(下称:DomString),转化为Dom对象
      • 临时Element+innerHTML+cloneNode返回最外层element元素对象,再进行插入appendChild,必要时还需要选择器方法讲某一个Element对象提取出来
      • XML字符串通过解析生成Element对象(注意,不是HTMLxxxElement对象,是Element对象),然后将该对象appendChild进去
  • 临时字符串(选择原因:借助innerHTML渲染,一次渲染)

###三、DocumentFragment的优缺点

基本模式:

var fragment = document.createDocumentFragment();
	fragment.appendChild(
    	...	//生成Element的IIFE
    )
//IIFE示例,根据配置创建元素
var ConfigVar = {
  ELname:"div",
  id:"blablabla",
  name:"balblabla",
  class:"ClassName"
}
(function(Config){
  	var el = document.createElement(Config.ELname);
  		el.className = (Config.class || "");
    for (let AttrName in Config){
      	if (AttrName == "class")continue;
      	el.setAttribute(AttrName,Config[AttrName]);
    }
  	return el;
})(ConfigVar)

优点

1、脱离于文档流,操作不会对Dom树产生影响

2、在每一次生成临时Element时候就可以将该Element对象的引用保存下来,而不需要多次用选择器再次获取。

缺点

兼容性只是达到IE9+

http://caniuse.com/#search=DocumentFragment

四、createElement的优缺点

基本模式

var el = document.createElement("ElementName");    
	el.className = "";
	el.setAttribute("AttrName",AttrValue);
	el.setAttribute("AttrName",AttrValue);
	...
	el.appendChild(        
  		... //生成Element的IIFE,见上文
    );

优点

1、新创建的元素脱离于文档流,操作不会对Dom树产生影响

2、兼容性最好

3、在每一次生成临时Element时候就可以将该Element对象的引用保存下来,而不需要多次用选择器再次获取。

缺点

每一次调用 setAttribute 方法都是一次次对Element进行修改, 此处具有潜在的性能损耗

五、DomString——临时Element+innerHTML+cloneNode的优缺点

基本模式

var domString2Dom = (function(){
    if (window.HTMLTemplateElement){
        var container = document.createElement("template");
        return function(domString){
            container.innerHTML = domString;
            return container.content.firstChild.cloneNode(true)
        }
    }else{
        //对不支持的template 的浏览器还有兼容性方法没写,所以不支持tr,td等些元素inner进div中。
        var container = document.createElement("div");
        return function(domString){
            container.innerHTML = domString;
            return container.firstChild.cloneNode(true)
        }        
    }
})();
var template = domString2Dom('<div class="TestClass" Arg="TestArg"></div>');
for (var index = 0; index < 80; index++) {    
  template.appendChild(
    (function(){
      var el = domString2Dom("<div>M</div>");
      return el
    })()
  )                
}

优点

创建Dom之后不需要多次进行 setAttribute

缺点

1、临时元素不能包裹一些特定的元素(不能在所有浏览器的所有 HTML 元素上设置 innerHTML 属性)

2、解析的过程进行了很多其余的操作。 此处具有潜在的性能损耗

3、插入的字符串第一层Node只允许有一个元素

六、DomString——XML解析的优缺点

基本模式

var XMLParser = function () {
    var $DOMParser = new DOMParser();
    return function (domString) {
        if (domString[0] == "<") {
            var doc = $DOMParser.parseFromString(domString, "application/xhtml+xml");
            return doc.firstChild;
        }
        else {
            return document.createTextNode(domString);
        }
    };
}();
var template = XMLParser('<div class="TestClass" Arg="TestArg"></div>');
for (var index = 0; index < 80; index++) {
  template.appendChild((function () {
    var el = XMLParser("<div>M</div>");
    return el;
  })());
}

优点

DomString方法中通用性最强的,虽然IE10+才支持DOMParser,但是IE9以下的有替代方法

缺点

1、解析的过程本身就具有潜在的性能损耗。

2、只能得到刚刚创建最外层元素的克隆。子元素的引用还需要用选择器。

3、插入的字符串第一层Node只允许有一个元素

七、临时字符串的优缺点

基本模式:

var template = document.createElement("div");
template.innerHTML = `<div class="TestClass" Arg="TestArg">
                        Test TextNode
                        ${(function(){
                          var temp = new Array(8);
                          for (var index = 0; index < 80; index++) {
                            temp[index]="<div>M</div>"
                          }
                          return temp.join()
                        }())}
                      </div>` //需要增加的一大段Element

优点

1、通用性最强,不需要逐步创建一大堆无用的Element对象引用

2、运用es6模板字符串编码优雅,不需要字符串用加号进行链接

缺点

1、如果是直接给出配置Config进行渲染需要进行字符串的生成

2、只能得到刚刚创建最外层元素的引用。子元素的引用还需要用选择器。

八、Template元素

由于HTML5中新增了template元素

其特点就是有一个content属性是HTMLDocumentFragment对象,所以可以包容任何元素

基本范式是:

var template = document.createElement("template");
template.innerHTML = `<div class="TestClass" Arg="TestArg">
                        Test TextNode
                        ${(function(){
                          var temp = new Array(8);
                          for (var index = 0; index < 80; index++) {
                            temp[index]="<div>M</div>"
                          }
                          return temp.join()
                        }())}
                      </div>` //需要增加的一大段Element
// template.content 是HTMLDocumentFragment

优点

比div要好很多,作为临时元素容器的包容性更强

缺点

兼容性不好: http://caniuse.com/#search=HTML%20templates 在不支持的浏览器中表示为HTMLUnknownElement

九、各种方法的效率对比

​ 测试代码:(由于笔者不太熟悉各种浏览器性能的BUG,这里的代码如果有不足请指正),代码由typescript进行编写,也可以用babel进行编译。

/**
 * @param Count:渲染DOM结构的次数
 */
var DateCount = {
    TimeList : {},
    time:function(Str){
        console.time(Str);
    },
    timeEnd:function(Str){
        console.timeEnd(Str);
    }
};
//==================工具函数======================
var domString2Dom = (function () {
    var container;
    if (window.HTMLTemplateElement) {
        container = document.createElement("template");
        return function (domString) {
            container.innerHTML = domString;
            return container.content.firstChild.cloneNode(true);
        };
    }
    else {
        //对不支持的template 的浏览器还有兼容性方法没写,所以不支持tr,td等些元素inner进div中。
        container = document.createElement("div");
        return function (domString) {
            container.innerHTML = domString;
            return container.firstChild.cloneNode(true);
        };
    }
})();
var XMLParser = (function () {
    var $DOMParser;
    if (window.DOMParser) {
        $DOMParser = new DOMParser();
        return function (domString) {
            if (domString[0] == "<") {
                var doc = $DOMParser.parseFromString(domString, "application/xhtml+xml");
                return doc.firstChild;
            }
            else {
                return document.createTextNode(domString);
            }
        };
    }else{
        $DOMParser = new ActiveXObject("Microsoft.XMLDOM");
        return function (domString) {
            if (domString[0] == "<") {
                $DOMParser.async = false;
                $DOMParser.loadXML(domString);   
                return $DOMParser
            }
            else {
                return document.createTextNode(domString);
            }                
        }
        
    }

})();
//===============================================

var Test = function(Count){
    //保留这种写法,能够在移动端平台中不依靠控制台进行效率测试
    // var DateCount = {
    //     TimeList : {},
    //     time:function(Str){
    //         this.TimeList[Str] = Date.now();
    //     },
    //     timeEnd:function(Str){
    //         alert(Str+(Date.now() - this.TimeList[Str]));
    //     }
    // }

        //基准测试1:
    DateCount.time("无临时div + 不需要字符串拼接 + innerHTML:")
    for (let index = 0; index < Count; index++) {
        (function(){
            var template = document.createElement("div");
                template.className = "TestClass";
                template.setAttribute("Arg","TestArg")
                template.innerHTML = ` Test TextNode
<div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div>
<div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div>
<div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div>
<div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div>
<div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div>
<div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div>
<div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div>
<div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div>
<div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div>
<div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div>
<div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div>
<div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div><div child="true">M</div>
` //需要增加的一大段Element,共100个子级div
            return template
        }())  
    }
    DateCount.timeEnd("无临时div + 不需要字符串拼接 + innerHTML:")

    //基准测试2:
    DateCount.time("createElement+appendChild写法:")
    for (let index = 0; index < Count; index++) {
        (function(){
            var template = document.createElement("div");
                template.className = "TestClass";
                template.setAttribute("Arg","TestArg")
                
                template.appendChild(document.createTextNode('Test TextNode'));
                for (let index = 0; index < 100; index++) {
                    let element = document.createElement("div");
                        element.setAttribute("child","true");
                        element.appendChild(document.createTextNode("M"))
                        template.appendChild(element)
                }
            return template
        }())  
    }
    DateCount.timeEnd("createElement+appendChild写法:")    

    //DocumentFragment 
    DateCount.time("DocumentFragment+ createElement+appendChild 写法:")
    for (let index = 0; index < Count; index++) {
        (function(){
            var fragment = document.createDocumentFragment();
                fragment.appendChild(function(){
                    var template = document.createElement("div");
                        template.className = "TestClass";
                        template.setAttribute("Arg","TestArg")
                        
                        template.appendChild(document.createTextNode('Test TextNode'));
                        for (let index = 0; index < 100; index++) {
                            let element = document.createElement("div");
                                element.setAttribute("child","true");
                                template.appendChild(element)
                        }
                    return template;
                }());

            return fragment
        }())  
    }
    DateCount.timeEnd("DocumentFragment+ createElement+appendChild 写法:")    

    //DomString——临时Element+innerHTML+cloneNode
    // DateCount.time("DomString——临时Element+innerHTML+cloneNode:")
    // for (let index = 0; index < Count; index++) {
    //     (function(){
    //         var template = domString2Dom('<div class="TestClass" Arg="TestArg"></div>');
    //         for (let index = 0; index < 100; index++) {    
    //             template.appendChild(
    //                 (function(){
    //                     var el = domString2Dom("<div child='true'>M</div>");
    //                     return el
    //                 })()
    //             )                
    //         }
    //         return template;
    //     }())  
    // }
    // DateCount.timeEnd("DomString——临时Element+innerHTML+cloneNode:")    

    //DomString——XML解析
    // DateCount.time("DomString——XML解析:")
    // for (let index = 0; index < Count; index++) {
    //     (function(){
    //         var template = XMLParser('<div class="TestClass" Arg="TestArg"></div>');
    //         for (let index = 0; index < 100; index++) {
    //             template.appendChild((function () {
    //                 var el = XMLParser("<div child='true'>M</div>");
    //                 return el;
    //             })());
    //         }
    //     }())  
    // }
    // DateCount.timeEnd("DomString——XML解析:")   

    //临时div + 临时字符串拼接:
    DateCount.time("临时div + 字符串拼接:")
    for (let index = 0; index < Count; index++) {
        (function(){
            let template = document.createElement("div");
            template.innerHTML = `<div class="TestClass" Arg="TestArg">
                                    Test TextNode
                                    ${(function(){
                                        let temp = "";
                                        for (let index = 0; index < 100; index++) {
                                            temp+="<div child='true'>M</div>"
                                        }
                                        return temp
                                    }())}
                                </div>` //需要增加的一大段Element
            return template.firstChild;
         }())  
    }
    DateCount.timeEnd("临时div + 字符串拼接:")

    //临时template + 临时字符串拼接:
    DateCount.time("临时template + 字符串拼接:")
    for (let index = 0; index < Count; index++) {
        (function(){
            var template = document.createElement("template");
            template.innerHTML = `<div class="TestClass" Arg="TestArg">
                                    Test TextNode
                                    ${(function(){
                                        let temp = "";
                                        for (let index = 0; index < 100; index++) {
                                            temp+="<div child='true'>M</div>"
                                        }
                                        return temp
                                    }())}
                                </div>` //需要增加的一大段Element
            return template.content;
         }())  
    }
    DateCount.timeEnd("临时template + 字符串拼接:")

    //临时template + createElement+appendChild 写法
    DateCount.time("template + createElement+appendChild 写法:")
    for (let index = 0; index < Count; index++) {
        (function(){
            var template = document.createElement("template");
                template.appendChild(function(){
                    var template = document.createElement("div");
                        template.className = "TestClass";
                        template.setAttribute("Arg","TestArg")
                        
                        template.appendChild(document.createTextNode('Test TextNode'));
                        for (let index = 0; index < 100; index++) {
                            let element = document.createElement("div");
                                element.setAttribute("child","true");
                                template.appendChild(element)
                        }
                    return template;
                }());
            return template.content
         }())  
    }
    DateCount.timeEnd("template + createElement+appendChild 写法:")

};

for (var key of [1,10,100,1000]) {
    console.log("Start"+key);
    Test(key);
}

十、结论

经过笔者基本依据手上平台进行测试,

  • 无临时div + 不需要字符串拼接 + innerHTML // createElement+appendChild写法:性能低,无论在桌面端还是移动端,在IE/Edge系还是 Webkit系都是同样的表现
  • domString 方法:性能最差
  • DocumentFragment+ createElement+appendChild 写法:性能在桌面WebKit端表现最好,移动端也有不错的表现
  • 字符串拼接:临时div + 字符串拼接/临时template + 字符串拼接:性能表现基本一致,桌面端WebKit上:比DocumentFragment(或tmplate) + createElement+appendChild性能差多了;与之相反,在IE系inneHTML的性能最高,是前者的x十倍。在移动端上两者性能相近,innerHTML略差一点点。
  • template + createElement+appendChild 写法:与DocumentFragment+ createElement+appendChild 写法效率相仿。

具体数据测试之后再补充。

(待续)


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

微商思维

微商思维

龚文祥、罗剑锋、触电会 / 金城出版社 / 2018-7 / 88.00元

微商不仅仅是一种继传统实体、电商之后的革命性新兴商业形态,更是一种能够写入中国商业史的思潮。龚文祥新著《微商思维》,从道的层面对广大微商人的商业实践智慧进行了高度浓缩与抽象总结,站在更高的视角解读微商背后的商业逻辑与本质。 本书前半部分,主要从本质、品牌、营销等几个方面,阐述了微商思维的内涵及应用场景,帮助读者了解并认识这种革命性的商业思维。 后半部分主要是触电会社群内部各位大咖的实操......一起来看看 《微商思维》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具