各种动态渲染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 写法效率相仿。
具体数据测试之后再补充。

(待续)

© 著作权归作者所有

文章转载自 开源中国社区 [http://www.oschina.net]

相关文章
|
机器学习/深度学习 人工智能 自然语言处理
【深度学习】python之人工智能应用篇——视频生成技术
视频生成技术是一种基于深度学习和机器学习的先进技术,它使得计算机能够根据给定的文本、图像、视频等单模态或多模态数据,自动生成符合描述的、高保真的视频内容。这种技术主要依赖于深度学习模型,如生成对抗网络(GAN)、自回归模型(Auto-regressive Model)、扩散模型(Diffusion Model)等。其中,GAN由两个神经网络组成:一个生成器用于生成逼真的图像或视频,另一个判别器用于判断生成的图像或视频是否真实。通过不断的对抗学习,生成器和判别器共同优化,以产生更高质量的视频。
616 2
|
Linux 虚拟化 内存技术
minos 1.2 内存虚拟化——guest
本文继续讲述 minos 中的内存虚拟化中关于 guest 的部分,主要弄清楚一个问题,minos 如何管理 guest vm 的内存。
232 5
minos 1.2 内存虚拟化——guest
|
前端开发 API Docker
web前端开发项目走proxy代理后端接口,构建发布到生产等环境后,如何修改api接口
web前端开发项目走proxy代理后端接口,构建发布到生产等环境后,如何修改api接口
262 0
|
存储 SQL 关系型数据库
面试官:你能聊聊 binlog、undo log、redo log 吗?
本文详细解析了MySQL数据库中的三种日志:binlog、undo log和redo log。binlog用于记录数据库的所有表结构变更及数据修改,支持归档、主从复制和数据恢复;undo log用于事务回滚,确保事务的原子性和实现多版本控制;redo log则用于crash-safe,确保数据库异常重启后已提交记录不丢失。文章通过实例和图表,深入浅出地介绍了每种日志的特点、应用场景及其实现机制。适合数据库开发者和运维人员阅读。
683 2
|
消息中间件 Arthas Java
RocketMQ—一次连接namesvr失败的案例分析
项目组在使用RocketMQ时遇到Consumer连接Name Server失败的问题,异常显示连接特定地址失败。通过Arthas工具逐步分析代码执行路径,定位到创建Channel返回空值导致异常。进一步跟踪发现,问题源于Netty组件在初始化`ByteBufAllocator`时出现错误。分析依赖后确认存在Netty版本冲突。解决方法为排除冲突的Netty包,仅保留兼容版本。
974 0
RocketMQ—一次连接namesvr失败的案例分析
|
IDE 开发工具 Windows
手把手教你调整电脑磁盘的分区大小
手把手教你调整电脑磁盘的分区大小
1480 0
手把手教你调整电脑磁盘的分区大小
|
搜索推荐 测试技术 流计算
承上启下:基于全域漏斗分析的主搜深度统一粗排
文章首先介绍了淘宝搜索的多阶段检索系统,包括召回、粗排和精排阶段。粗排模型的目标是优化商品的排序,以提高在召回集合中选择优质商品的能力。文章提到,粗排模型与精排模型的目标有所不同,粗排更注重腰部商品的排序,而精排更注重头部商品的排序。 此外,文章还探讨了模型的损失函数形式,发现原始的softmax损失函数在处理多正样本时存在问题,提出了改进的损失函数,使得模型在粗排阶段的表现更佳。最后,作者们总结了优化工作的进展,以及优化样本对齐,以实现更好的整体效果。
|
监控 数据可视化 图形学
重构U3D动画系统:运用Animator Controller层叠状态机优化游戏表现
【7月更文第11天】随着Unity 3D(简称U3D)游戏开发的不断深入,高效且流畅的动画系统成为了提升玩家体验的关键因素。本文将深入探讨如何通过重构U3D项目的动画系统,利用Animator Controller的层叠状态机(Layered State Machine)特性,显著提高动画的处理效率与游戏的流畅度。我们将通过一个实战示例,展示如何设置和优化状态机,进而实现角色动画的细腻控制与高效切换。
438 0
|
JSON Java 数据格式
JAVA对象jackson序列化json属性名首字母变成小写的解决方案
java代码对象如下: package com.ctrip.market.messagepush.
2854 0
|
NoSQL 关系型数据库 MySQL
Redis协议与异步方式
Redis协议与异步方式
337 0