1.为什么要优化JavaScript的性能?
性能是创建网页或应用程序时最重要的一个方面,没有人想要应用程序崩溃或者网页无法加载,或者用户的等待时间过长。大部分用户希望网站在2秒以内加载完成,如果加载过程需要 3 秒以上,则有 超过50%的用户会离开网站。
所以为了对这种状况进行改善,JavaScript的应用可谓是重中之重,对它的性能优化自然也凸显得尤为重要。
2.DOM 对JavaScript是最贵的性能开销,没有之一
首先,先让我们来了解一下什么是DOM:
DOM 是 W3C(万维网联盟)的标准。
DOM 定义了访问 HTML 和 XML 文档的标准:
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的时候,查找元素尽量一开始就查找完,再引用它,不要每次都去查找再引用,这样会导致第一,重复了代码;第二,性能很差。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!