译者:前端小智
作者:Felix Gerschau
起源:felixgerschau
点赞再看,微信搜寻 【大迁世界】 关注这个没有大厂背景,但有着一股向上踊跃心态人。本文 GitHub https://github.com/qq44924588... 上曾经收录,文章的已分类,也整顿了很多我的文档,和教程材料。

要比拟两个函数哪个性能更好,一个直观且偏心的办法就是计算两个函数别离执行完的工夫。

良好的性能更容易好的用户体验,而好的用户体验更能留住用户。 钻研表明,因为性能问题,在88%的在线消费者对用户体验不称心后,他们不太可能会二次应用。

这也是为什么要进步性能的一个重要起因。 特地是应用 JS 开发时,编写的每一行 JS 都可能会阻塞DOM,因为它是单线程语言。

本次分享,咱们次要介绍如何计算函数的性能。

Performance.now

Performance是一个做前端性能监控离不开的API,最好在页面齐全加载实现之后再应用,因为很多值必须在页面齐全加载之后能力失去。最简略的方法是在window.onload事件中读取各种数据。

performance.now()办法返回一个准确到毫秒的 DOMHighResTimeStamp

依据 MDN :

这个工夫戳实际上并不是高精度的。为了升高像Spectre这样的平安威逼,各类浏览器对该类型的值做了不同水平上的四舍五入解决。(Firefox从Firefox 59开始四舍五入到2毫秒精度)一些浏览器还可能对这个值作略微的随机化解决。这个值的精度在将来的版本中可能会再次改善;浏览器开发者还在考察这些工夫测定攻打和如何更好的缓解这些攻打。

因为,要计算一个函数的执行工夫,别离比拟函数执行前和执行后的两次 performance.now()的值即可,如下所示:

const t0 = performance.now();for (let i = 0; i < array.length; i++) {  // some code}const t1 = performance.now();console.log(t1 - t0, 'milliseconds');

在这里,咱们能够看到 Firefox 中的后果与 Chrome 齐全不同。 这是因为从版本60开始,Firefox 将performance API的精度升高到2ms

performance API 不当当只有返回工夫戳这个性能,还有很多实用办法,大家能够依据须要到 MDN 查问相干的文档。

然而,对于咱们的用例,咱们只想计算单个函数的性能,因而工夫戳就足够了。

performance.now() 和 Date.now一样吗?

你可能会想,嘿,我也能够应用Date.now来做?

是的,你能够,但这有毛病。

Date.now返回自Unix纪元(1970-01-01T00:00:00Z)以来通过的工夫(以毫秒为单位),并取决于零碎时钟。 这不仅意味着它不够准确,而且还不总是递增。 WebKit工程师(Tony Gentilcore)的解释如下:

基于零碎工夫的日期可能不太会被采纳,对于理论的用户监督也不是现实的抉择。 大多数零碎运行一个守护程序,该守护程序定期同步工夫。 通常每15至20分钟将时钟调整几毫秒。 以该速率,大概10秒距离的1%将是不精确的。

Performance.mark 和 Performance.measure

除了Performance.now函数外,还有一些函数能够让咱们度量代码不同局部的工夫,并将它们作为性能测试工具(如Webpagetest)中的自定义度量。

Performance.mark

先来看看MDN中对于mark办法的定义:

The mark() method creates a timestamp in the browser's performance entry buffer with the given name.

这段话能够合成出三个关键词。首先timestamp,这里的timestamp指的是高精度工夫戳(千分之一毫秒),其次是performance entry buffer

performance entry buffer指的是存储performance实例对象的区域,初始值为空。

最初就是given name,示意生成的每一个timestamp都有相应的名称。

所以这句话就能够了解成,在浏览器的performance entry buffer中,依据名称生成高精度工夫戳。也就是很多人说过的“打点”

就像Performance.now一样,此函数的精度分数高达5µs

performance.mark('name');

标记 的 performance entry将具备以下属性值:

  • entryType - 设置为 "mark".
  • name - 设置为mark被创立时给出的 "name"
  • startTime - 设置为 mark() 办法被调用时的 timestamp 。
  • duration - 设置为 "0" (标记没有持续时间).

Performance.measure

同样先来看看 MDN 上对于 measure 的定义:

这段定义和下面 mark 的定义有些相似,其最外围的不同点在于这句话 between two specified marks。所以measure是指定两个mark点之间的工夫戳。如果说mark能够了解为"打点"的话,measure就能够了解为"连线"

performance.measure(name, startMark, endMark);

计算两个mark之间的时长,创立一个DOMHighResTimeStamp保留在资源缓存数据中,可通过performance.getEntries()等相干接口获取。

  • entryType 为字符串 measure
  • name 为创立时设置的值
  • startTime为调用 measure 时的工夫
  • duration为两个 mark 之间的时长

从导航开始测量

performance.measure('measure name');

导航开始到标记

performance.measure('measure name', undefined, 'mark-2');

从标记到标记

performance.measure('measure name', 'mark-1', 'mark-2');

资源性能数据

从 performance entry buffer 获取数据

在下面的函数中,总是提到后果存储在performance entry buffer,然而如何拜访其中的内容呢?

performance API有3个函数能够用来拜访该数据:

performance.getEntries()

获取一组以后页面曾经加载的资源PerformanceEntry对象。接管一个可选的参数options进行过滤,options反对的属性有nameentryTypeinitiatorType

let entries = window.performance.getEntries();

performance.getEntriesByName

依据参数nametype获取一组以后页面曾经加载的资源数据。name的取值对应到资源数据中的name字段,type取值对应到资源数据中的entryType字段。

let entries = window.performance.getEntriesByName(name, type);

performance.getEntriesByType

依据参数type获取一组以后页面曾经加载的资源数据。type取值对应到资源数据中的entryType字段。

var entries = window.performance.getEntriesByType(type);

联合事例:

  performance.mark('mark-1');  // some code  performance.mark('mark-2')  performance.measure('test', 'mark-1', 'mark-2')  console.log(performance.getEntriesByName('test')[0].duration);

Console.time

这个 API的确易于应用。当须要统计一段代码的执行工夫时,能够应用console.time办法与console.timeEnd办法,其中console.time办法用于标记开始工夫,console.timeEnd办法用于标记完结工夫,并且将完结工夫与开始工夫之间通过的毫秒数在控制台中输入。这两个办法的应用办法如下所示。

console.time('test');for (let i = 0; i < array.length; i++) {  // some code}console.timeEnd('test');

输入的后果与Performance API十分类似。

console.time的长处是易于应用,因为它不须要手动计算两个工夫戳之间的差。

缩小工夫精度

如果在不同的浏览器中应用下面提到的 api 测量函数,你可能会留神到后果是不同的。

这是因为浏览器试图爱护用户免受时序攻打(timing attack)和指纹采集(Fingerprinting ),如果工夫戳过于精确,黑客能够应用它们来辨认用户。

例如,Firefox等浏览器试图通过将精度升高到2ms(版本60)来避免这种状况产生。

注意事项

当初,咱们曾经晓得了要测量JavaScript函数的速度所需办法。 然而,最好还要防止一些陷阱:

分而治之

开发过程中,咱们可能会我发现有些模块执行速度很慢,然而咱们不晓得具体问题出在哪里。解决一个办法是,应用下面提到的这些函数来测量它,而不是胡乱猜想代码的哪一部分比较慢。

要对其进行跟踪,首先将console.time语句放在执行比较慢的代码块四周。 而后测量它们不同局部的体现。 如果一个比另一个慢,那就持续往下走,直到发现问题所在。

留神输出值

在理论利用中,给定函数的输出值可能会产生很大变动。 仅针对任意随机值测量函数的速度并不能提供咱们能够理论应用的任何有价值的数据。

确保应用雷同的输出值运行代码。

屡次运行该函数

假如你有一个函数,它的功是遍历一个数组,对数组的每个值进行一些计算,而后返回一个带有后果的数组。你想晓得是forEach循环还是简略的for循环性能更好。

function testForEach(x) {  console.time('test-forEach');  const res = [];  x.forEach((value, index) => {    res.push(value / 1.2 * 0.1);  });  console.timeEnd('test-forEach')  return res;}function testFor(x) {  console.time('test-for');  const res = [];  for (let i = 0; i < x.length; i ++) {    res.push(x[i] / 1.2 * 0.1);  }  console.timeEnd('test-for')  return res;}

而后这样测试它们:

const x = new Array(100000).fill(Math.random());testForEach(x);testFor(x);

如果在 Firefox 中运行上述函数,后果:

看起来forEach慢多了,对吧?

那如果是雷同的输出,运行两次呢:

testForEach(x);testForEach(x);testFor(x);testFor(x);

如果咱们第二次调用forEach测试,它的执行成果和for循环一样好。思考到初始值较慢,在一些性能要求极高的我的项目,可能就不适宜应用forEach

在多个浏览器中测试

如果咱们在Chrome中运行上述代码,后果又会不一样:

这是因为Chrome和Firefox具备不同的JavaScript引擎,它们具备不同类型的性能优化。

在本例中,Firefox 在对雷同输出的forEach进行优化方面做得更好。

for在两个引擎上的性能都更好,因而在一些性能要求极高的我的项目就须要应用for循环。

这是为什么要在多个引擎中进行测量的一个很好的例子。 如果仅应用Chrome进行测量,你可能会得出结论,与for相比,forEach并不那么蹩脚。

限度的 CPU

咱们在本地测试值是不能代表用户在浏览器应用的状况,因为 咱们开发的电脑个别都会比大部分的用户好很多。

浏览器有一个个性能够限度CPU性能,咱们通过设置能够更贴切一些真实情况。

总结

在本文中,咱们看到了一些JavaScript API,咱们能够应用它们来掂量性能,以及如何在实在的我的项目中应用它们。 对于简略的测量,我发现应用console.time更容易。 如果要将测量与性能测量工具集成在一起,则可能须要应用performance.markperformance.measure

人才们的 【三连】 就是小智一直分享的最大能源,如果本篇博客有任何谬误和倡议,欢送人才们留言,最初,谢谢大家的观看。


编辑中可能存在的bug没法实时晓得,预先为了解决这些bug,花了大量的工夫进行log 调试,这边顺便给大家举荐一个好用的BUG监控工具 Fundebug。

原文:https://felixgerschau.com/mea...


交换

文章每周继续更新,能够微信搜寻 【大迁世界 】 第一工夫浏览,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 曾经收录,欢送Star。