开发者社区> atuanxy> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

前端优化系列 - JS解析性能分析

简介: 通常我们觉得页面已经写得非常好,已经没有优化空间了,但实际性能却不尽如人意。本文尝试从各个维度详细解析JS的性能消耗情况,找出导致页面性能大幅下降的根源。
+关注继续查看

前言

通常,我们觉得页面已经写得非常好,但性能却不尽如人意,在Trace就看到一大堆JS在执行,却不知在执行什么逻辑。JS执行为什么会这么耗时,它们到底在执行什么逻辑呢?

本文尝试从各个维度详细解析JS的性能消耗情况,找出导致页面性能大幅下降的真正杀手。

JS性能

一般来说,页面资源的性能消耗包括加载和执行。在加载方面,各类资源基本是平等的,主要与资源大小和网络有关。

在执行方面,差异就非常大,比如,

(1)图片的解码和渲染,可能在十毫秒级就处理完了;

(2)CSS解析,样式重计算,排版,估计在百毫秒级也可以处理完;

(3)而JS的Parse/Compile + JS Execution,可能好几秒才能完成。

关于JS的性能消耗,已经有非常优秀的文档,详细请参考:The Cost Of JavaScript

我们先来看一个页面完整的JS执行情况,

a4b549fe38d60f2ead054f0f50f3c59773e86098

从上图可以看到,页面的JS执行(v8.run)占了绝大部分时间,甚至单个JS执行超过2.8秒。那么,这些JS执行的消耗到底在哪里?是在执行业务逻辑,还是在干别的事情?

我们先看看一组基准测试的数据,

7423829d36bfa271eb5567ccd80f38a7bbe95133

按Octane基准测试,70%以上的时间消耗在JS Execution,而Parse/Compile 占比不到10%。

按Speedometer测试,Parse/Compile的耗时超过35%,而JS Execution的时间却很小。

这两类测试的数据并不一致,甚至完全相反。那么,那个测试更加准确呢?

很多数据表明,Speedometer测试能够更加准确的反映真实页面的性能,详细请参考:V8如何度量真实数据性能

JS解析

越来越多的数据表明,JS解析是JS性能消耗的主要部分。JS解析编译为什么会很耗时呢?其实,高级语言(C++)的编译更加耗时,一般来说,生成代码的优化程度越高解析编译过程就越耗时。

JS解析的耗时大概是怎样的呢?一般来说,在Nexus5手机上,生成100K的字节码需要消耗100-200ms的JS解析时间。

为什么要说明具体的测试机器呢,因为CPU对JS解析的影响非常大,iPhone8的性能可能是Nexus5的好几倍,所以一般应该选择中低端机器去进行测试。

那么,为什么现在JS解析会成为页面性能瓶颈呢?

我们先看看页面的平均JS大小是怎样的呢?根据HttpArchive统计,Nov 15,2010 页面平均JS大小为113K,而在 Nov 15,2017 页面平均JS大小为459K,其中top 1000站点的平均JS大小为617K,即JS大小一直在大幅增长。这些大小是指网络传输的大小,一般资源会经过gzip压缩再进行传输,所以资源的原始大小可能会更大,而ByteCode字节码的大小与原始大小接近。

从这些数据我们可以推断,页面的平均JS大小在不断增长,目前消耗在JS解析的时间已经非常惊人,全网平均时间会超过500ms。

我们再看看一些比较流行的前端框架的情况,现在基本每个页面都会引入一套前端框架,Angular,React,Vue 的大小情况如下

(1)angular2 gzip大小111K,原始大小566K

111K Jan 4 22:11 angular2.min.js.gz

566K Jan 4 22:03 angular2.min.js

(2)angular.1.4.5 gzip大小51K,原始大小143K

51K Jan 4 22:11 angular.1.4.5.min.js.gz

143K Jan 4 21:46 angular.1.4.5.min.js

(3)react-0.14.5 gzip大小39K,原始大小132K

39K Jan 4 22:11 react-0.14.5.min.js.gz

132K Jan 4 21:56 react-0.14.5.min.js

(4)vue-2.0.3 gzip大小23K,原始大小63K

23K Oct 13 03:02 vue-2.0.3.min.js.gz

63K Oct 13 03:02 vue-2.0.3.min.js

如果再加上一系列的依赖库会更大,知乎上的文章 提到react.js 加上依赖库,大小可达600K。

是不是JS框架文件越小性能越好?当然不是,良好的性能还需要框架和业务JS代码写得非常好。但是,如果JS框架文件非常大,JS解析的时间就决定了它的性能不会很好。比如,在首屏引入了一个600K的框架,就等同于在Nexus5手机上引入了600ms以上的性能损耗。

V8 Cache

JS解析这么耗时,为什么不将解析的结果缓存起来呢?的确如此,JS解析的ByteCode字节码是可以缓存的。那为什么不缓存执行效率更高的机器码呢?很多研究表明,缓存机器码的实际效果并不好。

Chrome V8在是否生成ByteCode字节码的过程,是走过弯路的。Chrome V8在2010年开始就采用双编译的架构,在2015年加入新的编译器TurboFan,全力去优化生成代码的质量。但这种架构,仅仅在实验室跑分上占优,在真实页面的性能上远远落后于JSC引擎。

0677ab3aeb9b3177c54637352d3b6414bbbefca7

2016年之后,Chrome V8团队成员进行了反思引入了新的解析器Ignition,使用解析器+编译器的架构(Ignition + TurboFan)并于Chrome 59版本默认打开,新的架构带来的收益非常明显,JS占用的内存大幅下降而实际页面的性能大幅提升。

0443b3956147723aaadba12be4903c89fbe1730f

那么,在真实世界上使用V8 Cache的效果是怎样的呢?

6da94533638a46a63541e9e490c0111dd5d8d096

上图是U4 2.0使用V8 Cache之前的Trace,JS执行耗时2826ms。(注:U4 2.0 是UC浏览器基于Chromium Blink打造的新一代浏览器内核)

fdc8a8f8a9b4ef0ba049504a01f4e2fc4f6b764b

上图是同一个JS,使用U4 2.0的V8 Cache之后的效果,JS执行耗时797ms,降到了原来的1/4,这个收益是非常明显的。

对很多页面来说,很有可能年度的性能优化目标,通过升级U4 2.0就可以实现了。

U4 2.0的V8 Cache这么美好,在什么条件下才能用上呢?

V8 Cache是根据JS文件的二进制内容生成,只要JS文件的二进制内容没有变化,都是可以用上的,包括页面关闭,浏览器重启,甚至是手机重启。特别说明一下,多个进程也可以共享同样的V8 Cache。也就是说,唯一会让V8 Cache失效的是JS文件二进制内容发生了变化。

为什么JS文件的内容会发生变化呢?一些容器在进行JS注入时,会往JS文本插入时间戳,这样就用不上V8 Cache了。

浏览器是否会自动清理V8 Cache呢?当然会的,因为V8 Cache文件还是挺大的,如果无限制,很容易会占满用户的存储空间。

结束语

在前端渲染非常流行的今天,页面的大部分逻辑都会通过JS去实现,复杂的业务逻辑让完全从头开始写一套JS代码变得几乎不可能,越来越多的JS框架被引进各类页面,页面的JS正变得越来越大。我们必须清醒的认识到,每100K的JS源代码可能意味着在中低端机器上100-200ms的性能损耗。

在JS性能方面,我们给的建议是,

(1)降低页面JS的大小,包括依赖库的JS大小,减少不必要的依赖。

(2)基于Vue,React,Angular,等JS框架的核心库去进行开发,不要一股脑引入一大堆依赖库。

(3)拆分首屏与非首屏逻辑,让执行耗时的JS尽可能在首屏之后执行。

(4)关注JS引擎的升级,JS引擎也在持续优化。对于阿里系的应用来说,需要关注UC内核的升级。

参考文档

JavaScript Start-up Performance

The Cost Of JavaScript

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
js:URL、URLSearchParams解析当前页面url和查询参数
js:URL、URLSearchParams解析当前页面url和查询参数
0 0
详细解析JavaScript中的全局属性和方法
详细解析JavaScript中的全局属性和方法 今天来补充JavaScript中的最后一部分内容,JavaScript中的全局属性和方法(函数)。 1.全局属性 // 属性 // Infinity 代表正的无穷大的数值。 // 在Javascript中,超出 1.7976931348623157E+103088 的数值即为Infinity,小于 -1.7976931348623157E+103088 的数值为无穷小。 var num1 = 1.7976931348623157E+103088; var num2 = -1.79769313486
0 0
详细解析JavaScript的Ajax之同步和异步的区别
详细解析JavaScript的Ajax之同步异步的区别 博客整理到这里基本上JavaScript的初级知识就整理完了,现在我们来说一下看JavaScript的最后一个知识点——Ajax。 (Asynchronous Javascript And XML) 即异步 JavaScript 和 XML,一种创建交互式、快速动态网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术。 1.同步和异步 首先我们先来分析一下JavaScript的中的同步和异步的区别。 同步就是调用某个内容时,调用方得等待这个调用返回结果才能继续往后执行。(期间不可以执行其他的命令)。如图:
0 0
简单解析JavaScript中的cookie对象
简单解析JavaScript中的cookie对象 前面说大了BOM的存储对象,现在简单了解一下cookie对象。 Cookie 用于存储 web 页面的用户信息。 大小限制为 4k。 安全性差。 1.创建cookie var aBtns = document.getElementsByTagName("button"); // 创建cookie document.cookie="name=value,name1=value1;expires= ;path=/" document.cookie = "name=小韩,n
0 0
简单解析JavaScript的默认事件及如何阻止默认事件
简单解析JavaScript的默认事件及如何阻止默认事件 上篇文章就提到,在JavaScript中提到事件冒泡两个必不可少也要提的就是事件捕获和默认事件,现在来聊一聊什么是默认事件,及如何阻止默认事件。 1.什么是默认事件 顾名思义,默认事件就是默认执行的事件,比如 a标签,点击a标签,页面会自动跳转。如图: 在这里插入图片描述 HTML代码: <form action=""> <input type="submit" id="submit"> <input type="image" src="../../CSS/0421/car.jpg"
0 0
简单解析js的事件冒泡及如何取消事件冒泡
简单解析js的事件冒泡 1.什么是事件冒泡? 以click点击事件为例。假如我们有一个多层结构标签。如下图,是4个div嵌套。每个div都有点击的监听事件,分别alert(“box4”),alert(“box3”),alert(“box2”),alert(“box1”)。当我们点击最里面的div时,点击事件开始传递。出现的效果是alert(“box4”)-alert(“box3”)-alert(“box2”)-alert(“box1”)。这就是事件冒泡阶段。效果如下: 在这里插入图片描述 HTML代码: <div class="box1"> <div clas
0 0
简单解析JavaScript中的正则表达式(三)
简单解析JavaScript中的正则表达式(三) 本片博客主要来和大家讲一下正则表达式实际的应用。 1.正则表达式的范围 [] 中括号用于查找某个范围内的字符: // [abc] 查找方括号之间的任何字符。 // [^abc] 查找任何不在方括号之间的字符。 除去 // [0-9] 查找任何从 0 至 9 的数字。 // [a-z] 查找任何从小写 a 到小写 z 的字符。 // [A-Z] 查找任何从大写 A 到大写 Z 的字符。 // [A-z] 查找任何从大写 A 到小写 z 的字符。 // [adgk] 查找给定集合内的任
0 0
简单解析JavaScript中的正则表达式(二)
简单解析JavaScript中的正则表达式(二) 上篇文章讲解了JavaScript中正则表达式的创建以及怎样在字符串方法中使用。这篇文章主要讲解正则表达式的属性和方法。 1.正则表达式的方法 // test() 测试 检索字符串中的值是否符合匹配的条件 true false var str="hello world"; var reg=/hellO/; console.log(reg.test(str)); // exec() 检索字符串中指定的值。 返回值的索引 没有 返回 null console.log(reg.ex
0 0
简单解析JavaScript中的正则表达式对象
简单解析JavaScript中的正则表达式对象 今天这篇篇博客来和大家讲一下JavaScript中的RegExp对象。 正则表达式:RegExp(Regular Expression)对象,简称正则式。时用来描述字符模式的对象,可以对字符串进行检索,匹配,替换等操作。 1.创建正则表达式 // 字面量 // 语法:var reg1=/表达式/修饰符 var reg=/a/igm; // 构造函数 // 语法:var reg2=new RegExp(表达式,修饰符) var reg2=new RegExp("a","igm"); /
0 0
简单解析JavaScript的Boolean对象
简单解析JavaScript的Boolean对象 Boolean 对象用于转换一个不是 Boolean 类型的值转换为 Boolean 类型值 (true 或者false)。 1.Boolean对象的创建 var bool=true;//true var bool2=new Boolean();//false 1 2 注意:当布尔对象没有初始值时或其值为 0,-0,null,"",false,undefined,NaN时,对象值为fasle,其他值都false。这个在后面的其他数据类型转换为布尔类型时有用到。 2.Boolean对象的属性 //
0 0
+关注
atuanxy
刘翔,阿里巴巴UC内核技术专家。负责UC内核性能优化及阿里集团前端页面优化,专注于Web体验优化和PWA技术研究。
文章
问答
文章排行榜
最热
最新
相关电子书
更多
Javascript中的函数
立即下载
Javascript异步编程
立即下载
JS零基础入门教程(上册)
立即下载