内容简介:每日一博 | 各种动态渲染 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 写法效率相仿。
具体数据测试之后再补充。
(待续)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Vue源码探究-虚拟DOM的渲染
- React SSR(服务器端渲染) 细微探究
- 跨域不完全探究
- Spring源码探究:容器
- Flutter BuildContext 探究
- Flutter mixins 探究
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Hit Refresh
Satya Nadella、Greg Shaw / HarperBusiness / 2017-9-26 / USD 20.37
Hit Refresh is about individual change, about the transformation happening inside of Microsoft and the technology that will soon impact all of our lives—the arrival of the most exciting and disruptive......一起来看看 《Hit Refresh》 这本书的介绍吧!