今天看啥  ›  专栏  ›  云伴风轻舞

浅谈JavaScript性能优化

云伴风轻舞  · 掘金  ·  · 2021-01-25 17:39
阅读 63

浅谈JavaScript性能优化

1.为什么要优化JavaScript的性能?

       性能是创建网页或应用程序时最重要的一个方面,没有人想要应用程序崩溃或者网页无法加载,或者用户的等待时间过长。大部分用户希望网站在2秒以内加载完成,如果加载过程需要 3 秒以上,则有 超过50%的用户会离开网站。

       所以为了对这种状况进行改善,JavaScript的应用可谓是重中之重,对它的性能优化自然也凸显得尤为重要。

2.DOM 对JavaScript是最贵的性能开销,没有之一

首先,先让我们来了解一下什么是DOM:

DOM 是 W3C(万维网联盟)的标准。

DOM 定义了访问 HTML 和 XML 文档的标准:

“W3C 文档对象模型 (DOM) 是中立于平台和语言的接口,它允许程序和脚本动态地访问和更新文档的内容、结构和样式。”

W3C DOM 标准被分为 3 个不同的部分:

  • 核心 DOM - 针对任何结构化文档的标准模型
  • XML DOM - 针对 XML 文档的标准模型(XML DOM 定义了所有 XML 元素的对象和属性
    ,以及访问它们的方法。)
  • HTML DOM - 针对 HTML 文档的标准模型(HTML DOM 定义了所有 HTML 元素的对象和属性,以及访问它们的方法。)

      简而言之,DOM就是文档对象模型,而HTML的文档document页面是一切的基础,没有它DOM就无从谈起。当创建好一个页面并加载到浏览器时,DOM就悄然而生,它会把网页文档转换为一个文档对象,主要功能是处理网页内容。 在这个文档对象里,所有的元素呈现出一种层次结构,就是说除了顶级元素html外,其他所有元素都被包含在另外的元素中。 

其次,JavaScript使用DOM元素性能开销大

使用JavaScript访问DOM元素很容易,代码更容易阅读,但是JS访问DOM元素没有我们想象的那么快,元素越多访问速度越慢。

这里做一个使用DOM元素的小测试,执行输出10000条数据:

<div id="container"></div>    
<script>       
 for(var count=0;count<10000;count++){            
document
     .getElementByid('container')
     .innerHTML+='<span>DOM小测试</span>;       
 }    
</script>
复制代码

通过Chrome浏览器打开后,会发现页面在不停地加载,过了相对比较长的一段时间才能加载出10000条数据,电脑要是性能差一些,页面会直接呈现无法响应的状态。

问题来了,为什么会这样呢?怎样优化?

因为数据执行了10000次吗?的确有这方面的原因 ,执行1次和执行10000 次,消耗的内存自然是不一样的 ,后者的内存花销更大,但是主要的原因并不是在这,我们先从优化的角度分析一下问题出现的原因,并解决问题。

- 一般来说,加载一个HTML页面是很快的,但是执行10000条数据后,页面却处于在不停地      加载状态。但其实这个HTML页面已经加载完成,不过其中的JS却堵塞了,要花时间等着JS执    行完成,这也是因为JS是一个单线程语言,它如果不执行完成,那么这个页面就会一直呈现      加载状态,造成的结果有两种,一种是页面加载超时,另一种是Chrome浏览器被限制使用      内存。

- 内存消耗在哪里呢?让我们分析一下代码,首先是for循环:

for(var count=0;count<10000;count++)
复制代码

  看上去循环10000次会消耗大量内存,但实际上不是,循环10000次其实是在短短1ms内完成,在操作系统CPU中,CPU运算时间是非常快速的。

  真正消耗大量内存的是document ,它才是主要的性能开销来源:

 document
                .getElementByid('container')
                .innerHTML+='<span>DOM小测试</span>;
复制代码

  首先,由于Chrome浏览器分配给每个tap进程都会有一定的内存最高值,这是操作系统对它的限制,执行10000条数据似乎就使页面处在崩溃的边缘,所以处于这样的运行环境下,可供Javascript支配的内存十分有限,而减少内存消耗,提高打开页面的效率就尤其重要。

  其次,document 即文档对象,HTML的文档document页面是一切的基础,没有它DOM就无从谈起,有了它我们就能通过JavaScript操纵DOM元素。这段代码通过document查找元素,一般来说查找一次就够了,但是这里查找了10000次,这是代码缺点导致的问题,对于性能优化没有概念的人往往会这样写代码。 

也许有人会说查找元素开销很小,但这里使用了DOM后开销就不一样了,别急,我们先从优化代码开始:  

<div id="container"></div>    
<script>        
let oContainer=document.getElementById('container');        
for(var count=0;count<10000;count++){
          oContainer.innerHTML+='<span>DOM小测试</span>';        
}    
</script>
复制代码

  通过重新定义一个对象,使用document.getElementById(),这样就省去了对元素9999次的      查找。

 那么DOM的开销到底为什么会这么大呢?

  当你看到一个页面时,它是分成两步打开的,这就涉及到一些底层原理。

 1.下载html,css,这是浏览器在渲染页面的第一步。

   这时会形成一个html的DOM树,这样才能实现树的查询。 因为html文件代码是一种一开一     关的结构,使用开关标签  如:, 就是为了形成这样一个DOM树。由于       浏览器本身是由C/C++写出来的,DOM如果要在页面上显示、查询,就必须形成一个tree       的结构, html和文件目录很相似,都是使用tree这样一种数据结构写出来的,所以会先生成     一个DOM树来形成html的结构和层次,body则是根节点,结点之下再挂结点, 用树的形式     能够很好地完成这一点。

   接着是css的解析执行,这是基于DOM树,生成css渲染树CSSOM,因为css有选择器selector,和属性构成一个Render tree,因为树结构执行得很快,所以就可以和html DOM树做匹配。

2.JavaScript=>Style=>Layout=>Paint=>Composite=>Reflow?=>Repaint?渲染DOM树和CSSOM树JavaScript规则合并DOM树和CSSOM树行程Render Tree  遍历渲染树开始局,计算每个节点的位置大小信息,将Render Tree的每个节点绘制在屏幕上reflow: 元素的几何尺寸发生了改变,需要重新验证并计算渲染树,是渲染树的一部分或者全部发生了变化repaint: 屏幕的一部分重画,不影响整体布局,比如某个CSS的背景色变了,但元素的几何尺寸和位置不变三、页面渲染过程优化.。

所以当我们写页面的时候,一定要把放在body的最后面,这是优化代码的第一条,我们不需要对这个页面进行操作,但是我们需要尽快地看到静态页面,要把页面显示出来,然后只要HTTP请求把这个html页面发给我们,就会立刻形成DOM树,检查有没有link,src标签,从而生成css渲染树CSSOM。

总而言之,操作DOM具体的开销成本,说到底是造成浏览器回流reflow和重绘reflow,从而消耗CPU资源,而这个过程如果重复10000次,消耗的内存自然会很大。

JavaScript是一个极其差性能的家伙:

1.JavaScript和Java比,性能相对较慢,因为Java是底层的内存操作级别语言,JavaScript只有它1/5的性能速度,所以和php,python等性能速度更快的脚本语言比拼是不现实的,但JavaScript的应用场景是目前唯一可以做前端的语言,虽说Go语言也初现端倪,但暂时还是无法和JavaScript竞争,而且JavaScript通过node可以做后端,移动端。 

2.JavaScript因为浏览器的缘故,在某种意义上来说是一个第三者,而DOM树和css渲染树是一对的,从而快速地把静态页面显示出来,这是第一要务。

编写一个监听事件:

document.addEventListener('DOMContentLoaded',()=>{            
console.log('DomContentLoaded');        
},false);       
window.addEventListener('load',()=>{            
console.log('loaded');        
},false)
复制代码

我们看看输出顺序:

很显然,DomContentLoaded先执行,而loaded后执行。产生这种状况是由于当“document.addEventListener('DOMContentLoaded',()=>{” 执行的时候,DOM和CSS结合完成了,显示静态页面,早一点输出;而当“window.addEventListener('load',()=>{”执行的时候,需要加载各种资源,如js,img,src...,要花大量的时间,所以运行输出较慢。

JS是第三者,当DOM树和css渲染树结合在一起时,它还能操作DOM,还能操作样式,它的使命就是操作动态DOM,实现动态页面,所以document.getElementById('')就会有巨大的开销,这个开销是来自从JS(语言世界)-> html+css DOM树世界,这一点是别的语言没有的,而DOM小测试中document.getElementById('')在这两个世界来回穿梭了10000次,开销自然是非常大的。

那么,优化后的DOM小测试还有优化的空间吗?

有的,而且关键就在这句代码:

oContainer.innerHTML+='<span>DOM小测试</span>'
复制代码

因为document读入消耗很大,innerHTML写入的消耗更大,所以据此进行代码优化:

 let oContainer=document.getElementById('container');//read let content='' for(var count=0;count<10000;count++){ content+='<span>我是一个小测试</span>';//js java c++ //read} oContainer      .innerHTML+=content;
复制代码

这10000次的操作我们是省不了的,所以声明一个content事件,让这10000次在这一个事件中做,做一个内存级别的字符串拼接,这是和JAVA,C++一样的语言级别的开销,因为只在JS的世界执行,所以这会执行的很快。

毕竟现在网站的页面都很大,像这样优化代码是很必要的,我们要理解JS(语言世界)和 html+css DOM树世界是两个世界,所以当我们遇到操作DOM树的地方,停下来一下,分析一下能不能优化一下,比如开始JS的时候,查找元素尽量一开始就查找完,再引用它,不要每次都去查找再引用,这样会导致第一,重复了代码;第二,性能很差。




原文地址:访问原文地址
快照地址: 访问文章快照