原文:https://alligator.io/js/js-performance-api/

性能,性能,性能,重要的事说三遍。你可能有世界上最好的网站,但如果加载它需要2分钟的话没有人会看它。如果你的网站需要2分钟来加载,要找到原因大概不会很难。但是当你想将平均加载时间从1秒降到0.85秒,优化就比较棘手了。

这里有很多工具可以帮助你理解应用程序在本地如何工作。Performance API可以帮助我们更细致地了解我们的页面。你可以得到真实的数据和查看你的网站在不同的浏览器、网络、世界各地等等是如何工作的。

Performance API通常被用来描述一组API。在一篇文章里全部描述它会有太多东西。在这篇文章里,我们将会展示最基本的功能来帮助你开始进行性能监控。

这个API还在不断进化中,将会出现很多新功能和废弃的功能。Performance Timeline Level 2马上就要来了;他们中有一些已经部分实现了,有一些仍然是草案。因此你需要经常去查看下MDN或者W3C的网站看看最近的更新。

如何访问性能数据

performance.now

测试一个程序的性能最基本的方式是使用performance.now()。它会返回一个亚毫秒的当前时间。如果你想要转换成高精度时间,我强烈推荐你阅读W3C草案的这篇文章

performance.now只能允许你测试JavaScript代码的内容(又称为用户性能)。在文章的后面我将会介绍一个关于使用performance.now例子

要访问不同的DOM和浏览器事件我们有3种方法:

  • getEntries()返回所有可用的性能条目。试着在当前页面运行performance.getEntries(),你将会看到一个巨大的数组。初始的大多数条目将与图片,脚本,以及其他通过页面加载的东西(也称为资源)相关。

    1. const tenthEntry = performance.getEntries()[10]
    2. // on Alligator.io it will return the following object
    3. // { initiatorType: "script",
    4. // nextHopProtocol: "h2",
    5. // workerStart: 526.8099999520928,
    6. // redirectStart: 0,
    7. // ....
    8. // decodedBodySize: 0,
    9. // serverTiming: [],
    10. // name: "https://d33wubrfki0l68.cloudfront.net/bundles/e2203d1b1c14952473222bcff4c58a8bd9fef14a.js",
    11. // entryType: "resource",
    12. // startTime: 315.5049999477342,
    13. // duration: 231.48499999661
    14. //}
    15. // We can see this is a resource entry for a script loaded from cloudfront
  • getEntriesByType() 与 getEntries()类似,但会给你一个筛选结果的可能性。

这里有6种类型可以查看:

  • frame: 一项非常实验性的功能,允许你获得关于在一次循环事件中浏览器完成了多少工作的数据。如果浏览器在一次循环中执行太多操作,frame率将会下降同时用户体验会变差。
  • resource:这个涉及到站点下载的所有资源。
  • mark: 这是自定义的标记可以被用来计算代码的速度。
  • measure: 使我们能够很容易地测量两个标记之间的差异。
  • paint:pain与屏幕像素的渲染有关。
  • longtask: 长任务是指执行时间超过50ms的任务。

在下一节我们会深入了解其中一些类型。这里先从一个简单的例子开始:

  1. const paintEntries = performance.getEntriesByType('paint')
  2. // paint Entries[0] equals {
  3. // name: "first-paint",
  4. // entryType: "paint",
  5. // startTime: 342.160000000149,
  6. // duration: 0,
  7. // }
  8. // paintEntries[1] equals {
  9. // name: "first-contentful-paint",
  10. // entryType: "paint",
  11. // startTime: 342.160000000149,
  12. // duration: 0,
  13. // }
  • getEntriesByName(entryName) 通过名字筛选所有条目。
    1. const nativeLogoPerfEntry = performance.getEntriesByName('https://alligator.io/images/alligator-logo3.svg')[0];
    2. // It will return performance information related to the logo's performance:
    3. // {initiatorType: "img",
    4. // nextHopProtocol: "",
    5. // workerStart: 539.6649999311194,
    6. // ........
    7. // name: "https://alligator.io/images/alligator-logo3.svg",
    8. // entryType: "resource",
    9. // startTime: 539.5149999530986,
    10. // duration: 94.24000000581145
    11. //}

如果你在寻找关于网站性能更高级的信息,你可以调用performance.toJSON()

审核你的函数

调整特定的JavaScript函数,最基本的工具是我们在上面讨论的performance.now()。
这里是一个用法示例:

  1. const firstNow = performance.now()
  2. // This loop is just to simulate slow calculations
  3. for (let i = 0; i < 100000; i++){
  4. var ii = Math.sqrt(i)
  5. }
  6. const secondNow = performance.now()
  7. const howLongDidOurLoopTake = secondNow - firstNow
  8. // on my laptop it returns howLongDidOurLoopTake == 4.08500000089407 in milliseconds

now有一个问题是如果你多次测量,它管理起来有点困难。一个更好的工具是mark,它能创建一些性能条目允许你稍后查看。然而你可以结合markers并使用measure创建新条目。

  1. performance.mark('beginSquareRootLoop');
  2. // This loop is just to simulate slow calculations
  3. for (let i = 0; i < 1000000; i++){
  4. var ii = Math.sqrt(i);
  5. }
  6. performance.mark('endSquareRootLoop');
  7. // Then anywhere in your code you can use
  8. // We create a new entry called measureSquareRootLoop which combines our two marks
  9. performance.measure('measureSquareRootLoop','beginSquareRootLoop', 'endSquareRootLoop');
  10. console.log(performance.getEntriesByName('beginSquareRootLoop'));
  11. // {detail: null,
  12. // name: "beginSquareRootLoop",
  13. // entryType: "mark",
  14. // startTime: 3745.360000000801,
  15. // duration: 0}
  16. console.log(performance.getEntriesByName('measureSquareRootLoop'));
  17. // {detail: null,
  18. // name: "measureSquareRootLoop",
  19. // entryType: "measure",
  20. // startTime: 3745.360000000801, This is the same as beginSquareRootLoop
  21. // duration: 9.904999984428287 shows the time it took to get from beginSquareRootLoop to endSquareRootLoop
  22. //}

Navigation数据

Navigation用来获得创建web页面的关键步骤的详细信息。访问navigation数据最安全的方式是:

  1. const navigationEntry = performance.getEntriesByType('navigation')[0]

在我的浏览器里,我得到:

  1. {
  2. unloadEventStart: 213.41000002576038,
  3. unloadEventEnd: 213.41000002576038,
  4. domInteractive: 975.8100000326522,
  5. domContentLoadedEventStart: 982.2649999987334,
  6. domContentLoadedEventEnd: 1217.9650000180118,
  7. domComplete: 2000.960000033956,
  8. loadEventStart: 2001.044999982696,
  9. loadEventEnd: 2008.6500000325032,
  10. type: "reload",
  11. redirectCount: 0,
  12. initiatorType: "navigation",
  13. nextHopProtocol: "",
  14. workerStart: 2.5550000136718154,
  15. redirectStart: 0,
  16. redirectEnd: 0,
  17. fetchStart: 2.5599999935366213,
  18. domainLookupStart: 2.5599999935366213,
  19. domainLookupEnd: 2.5599999935366213,
  20. connectStart: 2.5599999935366213,
  21. connectEnd: 2.5599999935366213,
  22. secureConnectionStart: 0,
  23. requestStart: 2.5599999935366213,
  24. responseStart: 107.46500000823289,
  25. responseEnd: 214.3950000172481,
  26. transferSize: 0,
  27. encodedBodySize: 0,
  28. decodedBodySize: 0,
  29. serverTiming: [],
  30. name: "https://alligator.io/",
  31. entryType: "navigation",
  32. startTime: 0,
  33. duration: 2008.6500000325032
  34. }

在后面的文章我们将详细介绍如何使用这些数据。但现在这里只展示一个可视化的导航时间轴。
performance-navigation-timeline.jpg

Resource

无论何时资源通过页面被加载,我们都可以在Performance Entries找到它的踪迹。我们获得他们要通过执行performance.getEntriesByType('resource')。它包括图片,脚本,CSS文件等等。所以假如我们想要关注网站上图片的性能我们可以运行:

  1. performance.getEntriesByType('resource').filter(resource=> resource.initiatorType == 'img')

这是一些Alligator.io上的资源:

  1. {
  2. initiatorType: "img",
  3. nextHopProtocol: "h2",
  4. workerStart: 551.2149999849498,
  5. redirectStart: 0,
  6. redirectEnd: 0,
  7. fetchStart: 551.3149999896996,
  8. domainLookupStart: 0,
  9. domainLookupEnd: 0,
  10. connectStart: 0,
  11. connectEnd: 0,
  12. secureConnectionStart: 0,
  13. requestStart: 0,
  14. responseStart: 0,
  15. responseEnd: 560.1850000093691,
  16. transferSize: 0,
  17. encodedBodySize: 0,
  18. decodedBodySize: 0,
  19. serverTiming: [],
  20. name: "https://d33wubrfki0l68.cloudfront.net/39d2d2905588dad289b228deb021d51449f6143d/a3baf/images/logos/gatsby-logo.svg",
  21. entryType: "resource",
  22. startTime: 222.0450000022538,
  23. duration: 338.1400000071153
  24. }

你可以看到这个的条目有很多值为0,这是因为我们受到CORS的限制(这是资源时间一个很大的限制)。所以相关的数据总是会返回0:redirectStart, redirectEnd, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, 和 responseStart。

Paint

paint API和窗口渲染像素的事件有关。正如我们在前面的片段中看到的,我们可以访问First Time to PaintFirst Contentful Paint。如果你有在用前端优化工具如Lighthouse,你可能会对他们感到熟悉。首次渲染是指在用户的屏幕出现第一个像素时。首次内容绘制是指当一个在DOM中定义的一个元素第一次被渲染。优化首次内容绘制你可以减少阻塞渲染的脚本和样式表,使用HTTP缓存,优化JavaScript的启动等等。

这些都是有用的指标,但如果你试图了解你的用户看到了什么,那这些指标是相当有限的。为了更好地了解用户的性能感知,我们需要结合更多指标。

performance API 非常庞大并且变化很快。查看更新最好的地方当然是Alligator.io,但如果你想要深入研究这个主题,你可以查看Web性能工作组的页面,你可以看到最新的工作草案和建议。