当前位置: 首页 > 科技观察

艺龙王海滨:前端渲染优化-domdiff

时间:2023-03-21 10:38:33 科技观察

对于不断增长的web应用,性能优化,用户体验从未间断,如何逆水行舟,不进则退撤退。随着通信技术的飞速发展,Web应用程序在近几年迅速增加并流行起来,已经成为人们不可或缺的工具,充斥在生活、商务、娱乐、旅游、工作的方方面面。随着用户规模的扩大,Web应用的内容和功能也越来越丰富。各大应用的用户体验、流量、内存、性能优化也越来越高。人们不仅需要看到自己需要的内容,还对响应速度、动画的流畅度、浏览网页的等待时间等提出了非常大的要求。对于网页首屏的优化,我们尝试使用异步加载页面数据来提高用户的流畅度,同时也增加了一些离线模板的技术规划。在代码的底层组件上,我们引入了新的方向,减少用户点击事件后对页面DOM节点的操作,从而提升用户体验。我们希望slarkjs是一个简单、通用、易于理解和使用的框架,我们的团队成员也保持一个正常的心态来丰富我们的框架。我们希望slarkjs是很多初级h5开发者想要了解的东西。为了熟悉,我会用很多很白话的概念思路来分析我们的框架组件,给一些对h5和slarkjs感兴趣的前端开发童鞋,了解一下组件开发思路和框架概念。回到dom优化,一开始我们打算用domdiff的概念来进行数据比较,而这些数据比较完全是在js中实现的,精简之后再进行dom操作。举个简单的例子,一个dom节点可能是这样的:

  • 1
  • 2
  • 3
  • 4
  • /ul>我们想把它变成这样
    • 1
    • 2
    • 3
    • 5
    • 6
    一般情况下,我们只有两种方法。第一种是替换整个ul节点。二是把你要的数据循环到里面,这样我们就有4次Delete,5次add,但是我们觉得这些dom操作太多了。其实在真实情况下,我们最需要替换的是第4里的数据,在最后加一个
  • 6/li>就可以达到我们需要的结果。我们需要一个组件来帮助我们操作dom节点进行分析。一般的domdiff应用存在于大部分聊天室、评论区,以及一些dom经常被替换的地方。我们希望它是一个小的、方便的应用程序,一个适合框架的小应用程序。在开发期间,我们也花了将近两周的时间,对现在非常流行的react和react-native进行了详细的技术调研。不得不说react的开发效率是我目前见过最快的框架。他模块化开发的思想,virtualdom的概念是我非常喜欢的一种方式,我们也尝试将其合并到slarkjs框架中。一开始我们只是希望让它负责视图层的重绘,但实际上我们更希望它能够负责更多的内容。不幸的是,在web层面使用react有一定的局限性,需要花费大量的开发时间去修改一些组件。不幸的是,我们暂时停止了这个项目的开发进度,但是在应用程序上开发react-native是最有潜力的壮举。在接下来的文章中,我们将继续为大家带来slarkjs框架如何吸收react-native并将其融入到app的开发中。.现在让我们回到domdiff的逻辑。首先,我们在构建domdiff的时候,思路很简单,1.我们需要它接收2个参数,1.现在页面上的节点,2.我们需要让它看起来像什么。vardomdiff=function(oldid,newid){vara1=document.getElementById(oldid);vara2=document.getElementById(newid);vardd=newdiffDOM();dd.apply(a2,dd.diff(a2,a1));};vartdomdiff=function(oldid,newid){vara2=document.getElementById(oldid);vara3=document.createElement('div');a3.innerHTML=newid;vardd=newdiffDOM();dd.apply(a2,dd.差异(a2,a3));};2、我们需要它比较两个参数的数据,并把它放回一个列表中,这个列表包含最少的DOM操作if(!tree1||!tree2){returnfalse;}if(tree1.nodeType!==tree2.nodeType){returnfalse;}if(tree1.nodeType===3){if(tree2.nodeType!==3){returnfalse;}returnpreventRecursion?true:tree1.data===tree2.data;}if(tree1.nodeName!==tree2.nodeName){returnfalse;}if(tree1.tagName===tree2.tagName){....}if(tree1.childNodes.length!==tree2.childNodes.length){returnfalse;}3.实现listObject.keys(options).forEach(function(t){diff[t]=options[t];});从开发的角度来看,1、3是非常容易实现的,而第二步会让大部分前端开发者感到头疼。这时候我们需要引入两个容易被遗忘、不经常使用的属性nodeTpye和childNodes。其实有很多JS的Attributes是我们或者在业务开发和技术实现中很少用到的。相对来说,这也影响了我们更深入的技术开发方向,所以很多时候,我们提倡看一些伟大的开发者的代码。其实我们想看看他们使用了哪些属性以及他们的开发逻辑思路,而不是照搬他们的代码NodeType,这样我们就可以得到body元素的节点类型。简单来说,就是让我们知道当前节点是一个元素,属性,文本内容等。ChildNodes会让我们获取body元素的子节点集合,作为一个NodeList对象。简单的解释就是返回一个列表,这个列表包含了当前节点下的所有子节点,包括class、text、select、option等。后面就很容易分析我们的思路了。使用NodeType获取节点,判断节点属性。当然你还需要判断当前页面的节点是否唯一。然后使用ChildNodes比较节点属性之间的差异,需要添加一些属性作为标签,比如判断当前是否应该修改,修改的顺序等等。OK,开始吧,于是就有了如下逻辑图(点击图片查看大图)我们在diff中上传了一个空的list数组,然后将2个nodeType传给了finddiff,finddiff会做两件事,就是判断在finddiff-out中是否在body中是唯一的,然后把里面的数据分离出来,把第一个修改项加入到链表中,也就是最外层的修改项。然后使用Finddiff-inner中的ChildNodes分析内部结构,循环判断两组数据是否重叠。这里有一个小问题,就是需要用距离值来填充匹配得到相同的内容。例如:
    • 1
    • 2
    • 3
    • 4
    而我们想做成这样
    • 1
    • 5
    • 6
    • 3
    • 4
    如果你只是去循环判断重复,那么第二步你会把5改成2,第三步把6改成3,这样很浪费资源,所以我们需要填写距离值,当我们使用新数据的时候循环时,我们需要在第一次循环中判断参数是否重复,判断重复参数的修改值为false,然后在第二次循环中为不重复的参数填充距离值数据,最后得到制作最简单的清单。这种方法减少了页面对DOM的操作次数,提高了页面加载率和二次加载率,但是也存在一些坑,例如:如果页面DOM修改量巨大,会浪费很多时间在环形。循环判断重复可能比单独替换整个dom节点需要更多的时间,所以在domdiff中需要加入一些判断来适应大多数方法。比如:减少循环,如果只是简单的文本替换,我们不需要循环去判断它的其他属性,或者提高阈值,如果运行时间或者数据量超过标准时间就进行局部dom替换,这些是代码的组件级严格性。说到底,domdiff其实是为了浏览器的优化而做的,但也必须要适应当前的环境。它更像是reactvirtualdom概念的前身。它有优点也有缺点。使用它时需要小心。我们会在未来的1、2个月内,对react-native进行详细的分析,并尝试将其融入到我们的框架中。或许我们会推出一个分支版本slarkjs-native来支持app的开发,届时我们会继续与大家分享更多的信息。技术经验,希望对h5比较感兴趣的小朋友可以加入我们的团队,体验Native的用户体验,同时保留React的开发效率。参考资料http://www.w3school.com.cn/jsref/prop_node_nodetype.asphttp://www.w3school.com.cn/jsref/prop_node_childnodes.asphttps://github.com/Seven-wang/react作者简介王海滨,8年互联网前端开发,曾就职于中颜票务通等互联网电商平台,2014年在艺龙网前端框架团队担任开发工程师,负责前端开发-艺龙网端框架。