持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情
循环问题
从常见的一个性能问题入手,循环会造成脚本运行时间过长。之前的循环优化如果还是没能减少足够的运行时间,那么你下一步就可以尝试的就是选用定时器。它的特性就是把循环的工作分解到一系列定时器中。
例如:
for(let i = 0, len = items.length; i < len; i++){
run(items[i]);
}
这类循环结构运行时间长的主要原因是 run 的复杂度和 items 的大小。是否可用定时器取代循环的两个决定性因素:
- 处理过程是否必须同步
- 数据是否必须按顺序处理
符合上诉条件之后,就可以适用于定时器分解任务:
function runArray(items, run, callback) {
const items2 = items.concat();
setTimeout(function(){
run(items2.shift());
if(items2.length > 0) {
setTimeout(arguments.callee, 25)
}else{
callback(items);
}
}, 25)
}
这个模式的基本思路是创建了一个原始数组的克隆,并将它作为数组项队列来处理。第一次调用 setTimeout() 创建了一个定时器处理数组的第一项。处理完后检查是否还有其他项,如果还有则再开启定时器,运行相同的代码,使用了 arguments.callee 实现。如果都处理完成,则调用 callback 回调函数。
分割任务
我们通常会把一个任务分解成一系列的子任务。如果一个函数运行时间过长,那么就可以尝试把他分解为能够在较短时间内完成的子任务。例如:
function savedocument(id){
// 保存文件
openDocument(id);
writeText(id);
closeDocuemnt(id);
// 将处理完成后的信息更新至界面
updateUI(id)
}
如果这个函数运行时间太长,可以很容易的把它拆分成一系列更小的步骤:
fuction saveDocument(id){
const tasks = [openDocument, writeText, closeDocument, updateUI];
setTimeout(function(){
const task = tasks.shift();
task(id);
if(tasks.length > 0){
setTimeout(arguments.callee, 25);
}
}, 25)
}
这个版本的函数把每个方法都放入 tasks 数组,然后每次在定时器中调用一个方法。唯一的区别在于处理数组条目时调用的函数就包含在条目中。再次封装以备复用:
function multistep(steps, args, callback){
const tasks = steps.concat();
setTimeout(function(){
const task = tasks.shift();
task.apply(null, args || []);
if(tasks.length > 0){
setTimeout(arguments.callee, 25)
}else{
callback();
}
}, 25)
}
multistep 函数接收三个参数:由函数组成的数组,为每个函数运行时提供参数的数组,结束后的回调。
定时器与性能
定时器会让你的 JavaScript 代码整体性能发生天翻地覆的变化。但是过度使用也会造成负面影响。当多个重复的定时器同时创建往往会造成性能问题。因为只有一个 UI线程, 而所有的定时器都在争夺运行时间。应当在你的 web 应用中限制高频率重复定时器的数量。