setTimeout()
JavaScript语言是单线程语言,它有一个叫做执行队列的东西来决定代码的执行顺序,而定时器的作用是:在特定的时间后将代码插入到执行队列。
这里要特别理解:定时器setTimeout(function, Interval)这里的Interval是指当Interval个单位时间过去之后将代码function插入到执行队列中,而不是过去Interval个单位时间之后执行function代码。也就说明代码的执行的时间将大于等于Interval。
使用 setTimeout
设置循环任务:当 num === 10
的时候,任务完成
let num = 0;
let max = 10;
let incrementNumber = function () {num++console.log(num)if (num < max) {setTimeout(incrementNumber, 500)} else {alert('任务完成,奖励自己一朵小红花')}
}
setTimeout(incrementNumber, 500);
![](https://i-blog.csdnimg.cn/blog_migrate/296e58b6cda40252ac9afd1bf1d2abc4.png)
有了 setTimeout
,那 setInterval
是否可以完成同样的任务呢?
let num = 0, intervalId = null;
let max = 10;
let incrementNumber = function () {num++console.log(num)if (num === max) {clearInterval(intervalId)alert('任务完成,奖励自己一朵小红花')}
};
intervalId = setInterval(incrementNumber, 500);
但是呢,一般来说,setIntervale()
在实践中很少会在生产环境下使用
setInterval()
接收两个参数:要执行的代码(字符串或函数),以及把下一次执行定时代码的任务添加到队列要等待的时间(毫秒)。
这里的关键点是,第二个参数,也就是间隔时间,指的是向队列添加新任务之前等待的时间。比如,调用 setInterval()
的时间为 10:00:00
,间隔时间为 3000 毫秒。这意 味着 10:00:03
时,浏览器会把任务添加到执行队列。浏览器不关心这个任务什么时候执行或者执行要花多长时间。因为一个任务结束和下一个任务开始之间的时间间隔是无法保证的,有些循环定时任务可能会因此而被跳过。
如何消除不准确
动态计算时差 (仅针对循环定时,只起修正作用 )
- 在定时器开始前和运行时动态获取当前时间,在设置下一次定时时长时,在期望值基础上减去当前时延,以获得相对精准的定时运行效果。
- 此方法仅能消除setInterval()长时间运行造成的误差累计,但无法消除单个定时器执行延迟问题。
var count = count2 = 0;
var runTime, runTime2;
var startTime, startTime2 = performance.now();//获取当前时间
//普通任务-对比
setInterval(function () {
runTime2 = performance.now();
++count2;
let time = runTime2 - (startTime2 + count2 * 1000)
console.log("普通任务", count2 + ' --- 延时:' + time + ' 毫秒');
}, 1000);
//动态计算时长
function func() {
runTime = performance.now();
++count;
let time = runTime - (startTime + count * 1000)
console.log("优化任务", count2 + ' --- 延时:' + time + ' 毫秒');
//动态修正定时时间
t = setTimeout(func, 1000 - time);
}
startTime = performance.now();
var t = setTimeout(func, 1000);
//耗时任务
setInterval(function () {
let i = 0;
while (++i < 100000000);
}, 0);
![](https://i-blog.csdnimg.cn/blog_migrate/46bc26d51227f246a1f1795276270e21.png)
相差其实不大
使用 Web Worker
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。
将该文件直接放入浏览器中运行是会报错的,可以使用 live-server 在本地起一个服务器,这样 worker 才会正常运行
<!-- index.html -->
<html>
<meta charset="utf-8">
<body><script type="text/javascript"> var count = 0;var runTime;//performance.now()相对Date.now()精度更高,并且不会受系统程序堵塞的影响。//API:https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/nowvar startTime = performance.now(); //获取当前时间 //普通任务-对比测试setInterval(function () {runTime = performance.now();++count;console.log("普通任务", count + ' --- 普通任务延时:' + (runTime - (startTime + 1000)) + ' 毫秒');startTime = performance.now();}, 1000);//耗时任务setInterval(function () {let i = 0;while (i++ < 100000000);}, 0);// worker 解决方案let worker = new Worker('./worker.js'); </script>
</body>
</html>
// worker.js
var count = 0;
var runTime;
var startTime = performance.now();
setInterval(function () {runTime = performance.now();++count;console.log("worker任务", count + ' --- 延时:' + (runTime - (startTime + 1000)) + ' 毫秒');startTime = performance.now();
}, 1000);
requestAnimationFrame
requestAnimationFrame是浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘。requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧,即是时间间隔为1000/60=16.7ms。 requestAnimationFrame 与 setTimeout相比,最大的优势在于requestAnimationFrame是由系统来决定回调函数的执行时机,充分利用显示器的刷新机制,比较节省系统资源。当页面处于不可见或不可用状态时,浏览器就会停止动画,这意味着更少的CPU和更少的内存消耗。
问题:大家都知道 setTimeout 属于宏任务,那么 requestAnimationFrame 是属于宏任务还是微任务? 先来看一道题:
setTimeout(() => {console.log(1)
});
requestAnimationFrame(() => {console.log(2)
})
setTimeout(() => {console.log(4)
})
是 2 1 4 ?还是 1 4 2。 别猜,去浏览器中指行一下,你会发现一下子是 2 1 4,一下子又是 1 4 2。这是咋回事,于是你懵逼了。。。。
它既不是宏任务,又不是微任务。 一个 setTimeout 在 requestAnimationFrame 的前面,它们里面的两个回调的顺序是不确定的。
场景:每隔10ms设置图像向右移动1px
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style> .box1 {width: 100px;height: 100px;background-color: aquamarine;}.box2 {margin-top: 100px;width: 100px;height: 100px;background-color: aquamarine;} </style>
</head>
<body><div class="box1"></div><div class="box2"></div><script> let box1 = document.querySelector('.box1');let box2 = document.querySelector('.box2');// setTimeoutlet num = 0;let max = 10000000;let degrees1 = 0;let incrementNumber = function () {num++box1.style.transform = "translateX(" + degrees + "px)";degrees1 = degrees1 + 1if (num < max) {setTimeout(incrementNumber, 10)}}setTimeout(incrementNumber, 10);// requestAnimationFramevar degrees = 0;function update() {box2.style.transform = "translateX(" + degrees + "px)";degrees = degrees + 1;window.requestAnimationFrame(update);}window.requestAnimationFrame(update) </script>
</body>
</html>
在浏览器预览中可以看到,两个方块都是匀速前进的。 这时,在绘制之前,在其中添加耗时的任务
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style> .box1 {width: 100px;height: 100px;background-color: aquamarine;}.box2 {margin-top: 100px;width: 100px;height: 100px;background-color: aquamarine;} </style>
</head>
<body><div class="box1"></div><div class="box2"></div><script> let box1 = document.querySelector('.box1');let box2 = document.querySelector('.box2');let num = 0;let max = 10000000;let degrees1 = 0;let incrementNumber = function () {num++box1.style.transform = "translateX(" + degrees + "px)";setInterval(function () {let i = 0;while (++i < 100000000);}, 0);degrees1 = degrees1 + 1if (num < max) {setTimeout(incrementNumber, 10)}}setTimeout(incrementNumber, 10);var degrees = 0;function update() {box2.style.transform = "translateX(" + degrees + "px)";setInterval(function () {let i = 0;while (++i < 100000000);}, 0);degrees = degrees + 1;window.requestAnimationFrame(update);}window.requestAnimationFrame(update) </script>
</body>
</html>
在浏览器中,使用 setTimeout 的方块会出现卡顿,但是使用 requestAnimationFrame 则是非常顺畅。