各種動態渲染Element方式的性能探究

2021-01-18 TechWeb

【引自雕刻零碎的博客】一、性能優化的原則及方法論

樹立原則:動態渲染進入一個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 寫法效率相仿。

具體數據測試之後再補充。

(待續)

點讚 0

相關焦點

  • 實時、動態、3D——渲染引擎新技術
    ICAD2007i基於渲染引擎進一步改進,深度發掘當前普通商用計算機的計算資源,使用戶可以使用只有工作站級系統才能體驗到的實時動畫渲染、動態實體剖切以及動態實體操作跟蹤所帶來的前所未有的操作快感,顯著提高用戶在三維實體編輯時的工作效率,有效加強三維設計功能的實用性。
  • Unity高級知識點總結:性能優化與圖形渲染進階
    比如我們常聽人說AlphaTest性能低,如果更進一步了解Early-Z、HSR、TBDR之後,就可以理解為什麼Unity要先渲染不透明物體,再渲染半透明物體。我們常聽人說Drawcall影響性能,更進一步了解渲染管線,知道什麼是Drawcall,什麼是渲染狀態,就會更進一步了解SetPassCalls是什麼東東。  理解本文中列舉的幾個關鍵知識點,也就可以對渲染優化有一定的概念了。
  • 封裝element-ui表格,我是這樣做的
    ,表格,分頁條的布局樣式需要監聽分頁的事件然後去刷新表格數據頂部按鈕或操作按鈕如果需要獲取表格數據,需要調用表格提供的api對於有行編輯的需求,還需要通過插槽去渲染行編輯的內容,同時要控制行編輯的開關不僅僅開發表格比較麻煩,而且還要考慮團隊協作,如果每個人實現表格的方式存在差別,那麼可能後期的維護成本也會變得很高。
  • React源碼之組件的實現與首次渲染
    react: v15.0.0 本文講 組件如何編譯 以及 ReactDOM.render 的渲染過程。 babel 的編譯 babel 將 React JSX 編譯成 JavaScript.
  • Vue 3下element-ui用不了怎麼辦,element-plus來幫你
    element-plus你可以理解為是element-ui支持Vue 3的版本,element-plus是一套支持Vue 3.0的組件庫,提供的組件涵蓋了絕大部分頁面UI的需求。在Vue 3的腳手架項目中,首先安裝element-plus的npm包,命令如下所示:npm install element-plus -S編輯main.js,引入整個element-plus組件和所需的樣式,由於element-plus組件內部默認使用英語,而我們項目需要使用中文
  • RTXDI 助力實時渲染數百萬束直射燈光
    一直以來,藝術家都受限於人造燈光的複雜性,實時渲染器根本無法支持大量動態燈光。多年來,NVIDIA一直在尋求解決這一問題以及實時渲染任意複雜照明的方法。我們所演示的NVIDIA Marbles at Night表明我們已實現了這一目標。
  • 掌趣技術專家分享:這些次世代手遊渲染技術,你一定能用上
    大家可以從截圖上看出,遊戲採用了PBR的渲染,場景當中有不少的動態光影效果,場景的細節也相當豐富。首先第一點,URP的特點它是一個單Pass的前向渲染管線,單Pass也就是說所有的動態光照是在一個Pass裡面完成計算的。單Pass最好的好處是,我們在添加動態光源的時候,不需要把場景裡面所有的物體再去渲染一遍。
  • 【AE插件】E3D插件 Element 3D v2.2.2.2168 Win/Mac 破解版 兼容CC 2019,附使用方法
    支持3D對象在AE中直接渲染的引擎,支持進行文件的導入、編輯、加上各種渲染特效等,讓圖片變得非常的炫酷,支持3D對象在AE中直接渲染或使用。E3D採用OpenGL程序接口。支持顯卡直接參與OpenGL運算,是AE中為數不多的支持完美3D渲染特性的插件之一。
  • Unity用戶手冊-正向渲染和延遲渲染
    正向渲染 & 延遲渲染 一、Forward Rendering(正向渲染) 發生在渲染管線的頂點處理階段,會計算所有的頂點的光照。全平臺支持。
  • 提高伺服電機動態性能的重要性
    隨著伺服電動機在工業中的廣泛應用,高動態性能的的伺服驅動器和伺服電動機的設計和研究必將成為國內研究的一個熱點,同時,如何提高伺服電動機的動態特性,也已經成為急待解決的問題。
  • 萬興科技PDFelement 8.0版本全新發布:像操作Word一樣操作PDF文檔!
    其中,文檔創意領域,為了更好地賦能個體和企業在數字經濟時代的競爭中脫穎而出,萬興科技正式推出了企業級一站式PDF解決方案PDFelement 8.0版本(中文版為萬興PDF專家),在產品性能、新增功能和交互體驗三大方面進行了全面升級。
  • 圖靈架構+眼球追蹤技術 開啟渲染新時代
    在GTC CHINA 2018上,RealDrive與七鑫易維協同亮相,向汽車行業的專家展示相關解決方案,此方案結合圖靈架構與眼球追蹤技術,開啟了渲染新時代RealDrive使用VR技術結合駕駛模擬器展示虛擬駕駛和自動駕駛仿真平臺體驗系統。
  • Laravel + Element 超簡單實現分頁效果案例教程
    size=3&page=2二、vue-element-admin 前端框架1、Element-UI 是基於 Vue 2.0 的桌面端組件庫,內置有 Pagination 分頁組件。vue-element-admin 是一個後臺前端解決方案,它基於 vue 和 element-ui 實現,vue-admin-template 是其簡化版。
  • 動態力學性能分析的利器— DMA Eplexor
    原理  Eplexor系列是大力值DMA 儀器,可以在動態或靜態載荷的情況下對材料進行表徵:  1、動態載荷測試,是在樣品上施加一定頻率的周期應力,分析應變大小及施加的動態力與樣品形變間的相位差,由此得到材料的動態性能,如剛度(彈性模量,E』)和阻尼(損耗模量,E』』)。
  • unity 半透明渲染技巧(3則)
    unity 半透明渲染技巧(1):固定深度法半透明渲染排序問題 長期在各種3d引擎存在,這裡將一些針對性技巧。
  • 【插件】最強的AE插件Element 3D
    3D基於對象的粒子插件,使用快速的Open GL 3D渲染引擎。在AE載入和三維動畫格式:支持最流行的3D軟體OBJ文件格式和C4D文件。沒有多邊形限制!Element 3D支持UV貼圖坐標,所以很容易重建,並導入您的貼圖!Element 3D採用了獨特的粒子陣列系統,該系統可以分發3D對象成任何形狀!用真實的3D物體粒子。很容易擠壓和文本導角和遮罩形狀!新版本兼容AE CS5及以上版本。
  • 藉助WebGL三維可視化技術檢索3D動態圖像
    在解決了海量數據分析耗時過長、挖掘深度不夠、數據展現簡單等問題的基礎上,大數據可視化平臺使人們不再局限於使用傳統關係數據表來分析數據信息,而是以更直觀的方式呈現和推導數據間的邏輯關係。總而言之,數據可視化是做大數據分析的一個很重要的手段。WebGL光柵化數據渲染引擎,基於GPU渲染,GPU是數以千計的高效並行核心組成,在圖像處理渲染方面有優勢。
  • PRB成為建模大熱的渲染方式,到底優秀在哪裡?
    PBR,Physically Based Rendering,這是一種全新的渲染方式,其對應的是一種全新的工作流程;在PBR流程下,遊戲中場景表現將更加符合物理規則,對於光照的計算也更符合現實,PBR的目標是基於物理的渲染,這技術對目前視覺開發而言是一種變革性技術。
  • 功夫熊貓2揭秘 100TB數據+5500小時渲染
    「中華元素大亂燉,3D效果太銷魂」,惠普工作站渲染農場5500多萬小時不間斷渲染,惠普Z800旗艦工作站擔任著「馬良的神筆」。  作為夢工廠的唯一IT技術供應商以及夢工廠長期合作的重要夥伴,《功夫熊貓2》的「修煉」過程自然少不了來自惠普工作站技術夥伴的鼎力支持與全程助力。
  • 如何在Vue3框架中使用Element Plus
    run dev運行項目6、按照訪問地址,預覽界面效果按照訪問地址,預覽界面效果7、按Ctrl + C快捷鍵,停止項目;然後使用命令npm i element-plus安裝element-plus安裝element-plus8、打開main.js文件,導入element-plus的組件