setTimeout() 和 setIntervale() 小结

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); 

有了 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); 

相差其实不大

使用 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 则是非常顺畅。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值