各種動態(tài)渲染Element方式的性能探究
【引自雕刻零碎的博客】一、性能優(yōu)化的原則及方法論
樹立原則:動態(tài)渲染進入一個Dom元素,首先需要保證動態(tài)渲染操作必須盡可能少對原有dom樹的影響,影響重繪及重排。
確定方法論:必須尋找一個容器來緩存渲染期間生成的dom結(jié)構(gòu)(操作必須盡可能少對原有dom樹的影響),然后再進行一次渲染到目標(biāo)element中。
二、生成期間DOM緩存的選擇
- DocumentFragment(文檔碎片對象,選擇原因:脫離于文檔流)
- 臨時Element(選擇原因:新element脫離于文檔流)
- createElement,再一步步進行渲染
- 通過描述Dom的String(下稱:DomString),轉(zhuǎn)化為Dom對象
- 臨時Element+innerHTML+cloneNode返回最外層element元素對象,再進行插入appendChild,必要時還需要選擇器方法講某一個Element對象提取出來
- XML字符串通過解析生成Element對象(注意,不是HTMLxxxElement對象,是Element對象),然后將該對象appendChild進去
- 臨時字符串(選擇原因:借助innerHTML渲染,一次渲染)
三、DocumentFragment的優(yōu)缺點
基本模式:
- var fragment = document.createDocumentFragment();
- fragment.appendChild(
- ... //生成Element的IIFE
- )
- //IIFE示例,根據(jù)配置創(chuàng)建元素
- 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)
優(yōu)點
1、脫離于文檔流,操作不會對Dom樹產(chǎn)生影響
2、在每一次生成臨時Element時候就可以將該Element對象的引用保存下來,而不需要多次用選擇器再次獲取。
缺點
兼容性只是達(dá)到IE9+
http://caniuse.com/#search=DocumentFragment
四、createElement的優(yōu)缺點
基本模式
- var el = document.createElement("ElementName");
- el.className = "";
- el.setAttribute("AttrName",AttrValue);
- el.setAttribute("AttrName",AttrValue);
- ...
- el.appendChild(
- ... //生成Element的IIFE,見上文
- );
優(yōu)點
1、新創(chuàng)建的元素脫離于文檔流,操作不會對Dom樹產(chǎn)生影響
2、兼容性***
3、在每一次生成臨時Element時候就可以將該Element對象的引用保存下來,而不需要多次用選擇器再次獲取。
缺點
每一次調(diào)用setAttribute方法都是一次次對Element進行修改,此處具有潛在的性能損耗。
五、DomString——臨時Element+innerHTML+cloneNode的優(yōu)缺點
基本模式
- 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
- })()
- )
- }
優(yōu)點
創(chuàng)建Dom之后不需要多次進行setAttribute
缺點
1、臨時元素不能包裹一些特定的元素(不能在所有瀏覽器的所有 HTML 元素上設(shè)置 innerHTML 屬性)
2、解析的過程進行了很多其余的操作。此處具有潛在的性能損耗。
3、插入的字符串***層Node只允許有一個元素
六、DomString——XML解析的優(yōu)缺點
基本模式
- 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;
- })());
- }
優(yōu)點
DomString方法中通用性***的,雖然IE10+才支持DOMParser,但是IE9以下的有替代方法
缺點
1、解析的過程本身就具有潛在的性能損耗。
2、只能得到剛剛創(chuàng)建最外層元素的克隆。子元素的引用還需要用選擇器。
3、插入的字符串***層Node只允許有一個元素
七、臨時字符串的優(yōu)缺點
基本模式:
- 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
優(yōu)點
1、通用性***,不需要逐步創(chuàng)建一大堆無用的Element對象引用
2、運用es6模板字符串編碼優(yōu)雅,不需要字符串用加號進行鏈接
缺點
1、如果是直接給出配置Config進行渲染需要進行字符串的生成
2、只能得到剛剛創(chuàng)建最外層元素的引用。子元素的引用還需要用選擇器。
八、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
優(yōu)點
比div要好很多,作為臨時元素容器的包容性更強
缺點
兼容性不好:http://caniuse.com/#search=HTML%20templates 在不支持的瀏覽器中表示為HTMLUnknownElement
九、各種方法的效率對比
測試代碼:(由于筆者不太熟悉各種瀏覽器性能的BUG,這里的代碼如果有不足請指正),代碼由typescript進行編寫,也可以用babel進行編譯。
- /**
- * @param Count:渲染DOM結(jié)構(gòu)的次數(shù)
- */
- var DateCount = {
- TimeList : {},
- time:function(Str){
- console.time(Str);
- },
- timeEnd:function(Str){
- console.timeEnd(Str);
- }
- };
- //==================工具函數(shù)======================
- 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]));
- // }
- // }
- //基準(zhǔn)測試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:")
- //基準(zhǔn)測試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);
- }
十、結(jié)論
經(jīng)過筆者基本依據(jù)手上平臺進行測試,
- 無臨時div + 不需要字符串拼接 + innerHTML // createElement+appendChild寫法:性能低,無論在桌面端還是移動端,在IE/Edge系還是 Webkit系都是同樣的表現(xiàn)
- domString 方法:性能最差
- DocumentFragment+ createElement+appendChild 寫法:性能在桌面WebKit端表現(xiàn)***,移動端也有不錯的表現(xiàn)
- 字符串拼接:臨時div + 字符串拼接/臨時template + 字符串拼接:性能表現(xiàn)基本一致,桌面端WebKit上:比DocumentFragment(或tmplate) + createElement+appendChild性能差多了;與之相反,在IE系inneHTML的性能***,是前者的x十倍。在移動端上兩者性能相近,innerHTML略差一點點。
- template + createElement+appendChild 寫法:與DocumentFragment+ createElement+appendChild 寫法效率相仿。
具體數(shù)據(jù)測試之后再補充。
(待續(xù))