各种动态渲染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 写法效率相仿。

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

(待续)

转载于:https://my.oschina.net/reamd7/blog/916530

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值