FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
362 stars 39 forks source link

[译]渲染性能优化之JS篇 #196

Open FrankKai opened 4 years ago

FrankKai commented 4 years ago

原文链接:https://developers.google.com/web/fundamentals/performance/rendering/optimize-javascript-execution#reduce_complexity_or_use_web_workers

TL;DR

使用requestAnimationFrame处理可视改变

当屏幕上发生可视改变时,你想让浏览器在正确的时间做事情,也就是在一个frame开始的时候。确保js在frame开始时运行的唯一方式是requestAnimationFrame。

/**
 * If run as a requestAnimationFrame callback, this
 * will be run at the start of the frame.
 */
function updateScreen(time) {
  // Make visual updates here.
}

requestAnimationFrame(updateScreen);

框架或者例子也许会用setTimeout或setInterval去做类似动画的可视改变,但是问题是callback会在frame的某一个点上运行,也许会在结尾,可能会导致我们错过一个frame,导致jank。

image

事实上,jQuery在animate函数中用了setTimeout。在v3中换成了requestAnimationFrame。

降低复杂性或者使用Web Workers

js运行在浏览器的主线程上,包括样式计算,layout但是更多的时候是在做paint。如果你的js运行需要很长时间,它会阻塞其他的任务,会明显导致帧丢失。

在js运行时,你需要有自己的策略,比如说多久。例如,如果你在运行一个scrolling动画,理想情况的js运行最好在3ms到4ms。超过这个时间就占据太多了。如果是在空闲期,当然可以花费更多时间在上面。

很多情况下可以把一些纯计算的工作移动到web worker,比如不需要引用DOM节点的。数据处理或转换,比如排序和搜索,通常很适合。

var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);

// The main thread is now free to continue working on other things...

dataSortWorker.addEventListener('message', function(evt) {
   var sortedData = evt.data;
   // Update data on screen...
});

并不是所有的工作都适合这个模型:Web工作者没有DOM访问权限。如果您的工作必须在主线程上,那么可以考虑使用批处理方法,将较大的任务分割为多个微型任务,每个任务的耗时不超过几毫秒,并在requestAnimationFrame处理程序中跨每一帧运行。

var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);
requestAnimationFrame(processTaskList);

function processTaskList(taskStartTime) {
  var taskFinishTime;

  do {
    // Assume the next task is pushed onto a stack.
    var nextTask = taskList.pop();

    // Process nextTask.
    processTask(nextTask);

    // Go again if there’s enough time to do the next task.
    taskFinishTime = window.performance.now();
  } while (taskFinishTime - taskStartTime < 3);

  if (taskList.length > 0)
    requestAnimationFrame(processTaskList);

}

这种方法会产生UX和UI方面的后果,您需要确保用户知道正在处理的任务,方法是使用进度或活动指示器。在任何情况下,这种方法将保持你的应用程序的主线程自由,帮助它保持对用户交互的响应。

具体如何使用webworker,可以参考:一次失败的用web worker提升速度的实践

了解你的JavaScript ”frame tax“

在使用js框架,库或者自己的代码的时候,需要逐帧知道它们的消耗。 对于transitioning和scrolling这种性能严格的动画,知道消耗是很重要的。

image

在Main里面详细标明了每段代码消耗了多少时间。

避免微优化你的JavaScript

知道浏览器可以执行的一个版本比另一件东西快100倍是一件很酷的事,请求元素的offsetTop比getBoundingClientRect()更快,但这是几乎总是正确的。你只会被调用函数这样一个小的每帧,所以通常浪费努力专注于这方面的JavaScript的性能。通常只会节省几毫秒的时间。

如果你正在制作一款游戏,或者是一个需要大量计算的应用程序,那么你很可能是这个指南的一个例外,因为你通常会将大量的计算放入一个单独的框架中,在这种情况下,一切都是有帮助的。

简而言之,您应该对微优化非常谨慎,因为它们通常不会反应到您正在构建的应用程序的类型。

核心思想就是:不要花费巨大的努力在微小的优化上,耗时耗力而且效果不明显。