0%

优化JS

TL;DR

  • 避免对可视化更新使用 setTimeoutsetInterval; 始终使用 requestAnimationFrame

  • 将长时间运行的 JavaScript 从主线程移动到 Web Workers

  • 使用微任务进行跨越多个帧的 DOM 更改

  • 使用开发者工具的timeline和JavaScript Profiler评估JS性能

使用requestAnimationFrame改变视觉表现

requestAnimationFrame可以在帧的开始运行一个指定的回调。

1
2
3
4
5
6
7
8
9
/**
* 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 来进行动画等可视化更改,但问题是回调会在帧中的某个点运行,可能就在帧的最后,这通常会导致我们错过一个帧,从而导致 jank

jQuery过去使用setTimeout处理动画行为,在版本3之后改用requestAnimationFrame,强烈建议patch旧版本的jQuery。

减少复杂性或者用Web Workers

js运行在主线程,还有其他注入样式计算布局和绘制等任务。如果JS运行太久就会阻塞这些任务,导致丢帧。

如果使用滚动动画,将JS保持3到4ms。如果处于idle时期,就无所谓了。

许多情况下,不需要DOM访问的计算操作可以转移到web workers。例如排序或者搜索非常适合这个模型,加载和生成模型也可以。

1
2
3
4
5
6
7
8
9
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...
});

并不是所有的工作都适合这种模式: webworkers 没有 DOM 访问权限。如果您的工作必须在主线程上进行,那么考虑一种批处理方法,将较大的任务分割为微任务,每个任务的执行时间不超过几毫秒,并在 requestAnimationFrame 处理程序内部跨每一帧运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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,您需要确保用户知道任务正在被处理,通过使用进度或活动指示符。在任何情况下,这种方法都可以保持应用程序的主线程空闲,帮助它保持对用户交互的响应。