域名

各种动态渲染Element方式的性能探究

时间:2010-12-5 17:23:32  作者:数据库   来源:域名  查看:  评论:0
内容摘要:【引自雕刻零碎的博客】一、性能优化的原则及方法论树立原则:动态渲染进入一个Dom元素,首先需要保证动态渲染操作必须尽可能少对原有dom树的影响,影响重绘及重排。确定方法论:必须寻找一个容器来缓存渲染期

【引自雕刻零碎的各种博客】一、性能优化的动态原则及方法论

树立原则:动态渲染进入一个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对象的引用保存下来,而不需要多次用选择器再次获取。云南idc服务商

缺点

兼容性只是达到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 写法效率相仿。

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

(待续)

copyright © 2025 powered by 益强资讯全景  滇ICP备2023006006号-31sitemap