setTimeout 详解
要明确的是:本文不太适合新手阅读,需要有一定只是铺垫(比如你要知道堆和栈,宏任务和微任务的概念等)
首先我们应明确有关settimeout的概念:
setTimeout是异步执行的,堆栈中碰到setTimeout会交给浏览器内核处理,等待setTimeout达到触发条件(即设定的时间),再返回给执行队列。简而言之,就是计时的这个操作是在浏览器端进行的,在计时完成后,将settimeout中的操作放入事件队列中
下面我们就通过例子来验证上面的概念
首先看一个例子:
setTimeout(() => {
console.log('计时器1 计时三秒');
}, 3000);
setTimeout(() => {
console.log('计时器2 计时一秒')
}, 1000);
这个很简单,结果是:
// 计时器2 计时一秒
// 计时器1 计时三秒
由此可以看出,在几个计时器同时执行时,并不是按照代码顺序执行,这也就说明了,settimeout是异步执行,和顺序无关,只和计时时长有关
上面代码指的是:两个计时器都同时在浏览器上开始计时,当第二个计时器到计时时长后,浏览器就将其推送到js线程中,因为js线程是空的,所以立即执行,而第一个计时器还在计时中,当它计时完成(3秒),然后浏览器将其推送到js线程中,因为此时js线程空闲,所以也立即执行
再看下面的例子:
setTimeout(() => {
console.log('计时器')
}, 3000);
console.time();
for (let index = 0; index < 10000000; index++) {
index.toString = '这是1' + index;
// console.log()
}
console.timeEnd();
// default: 5030.498779296875ms
// 计时器
在5030ms(每次执行时间不定,误差在100ms内)后,两个打印先后出现
有人会疑问:我计时器只有3秒啊,它是在浏览器端执行的,跟JS没关系啊
原因就是:JS线程和浏览器互不影响,浏览器只是在计时结束后将计时器中的逻辑推送到JS线程中
所以上面的代码执行流程是:JS执行到setTimeout时,将其交给浏览器去计时(哪怕时间是0),然后去执行同步的for循环,因为例子中的for循环执行需要5s左右,在for循环执行3秒时,浏览器已经把之前的计时器计时完毕,然后推送到JS的事件队列里,当做下一个task任务执行,当前任务(for循环)不受影响,当for循环结束后,JS线程空了,然后去事件队列中取微任务或者新的任务(setTimeout),然后执行。
PS:计时器和上面的default不是严格意义上的同时打出,计时器会稍稍延后一些。
进阶:在上面例子上再加一个计时器呢?
setTimeout(() => {
console.log('3秒计时器');
}, 3000);
console.time();
for(let index = 0; index < 10000000; index++) {
index.toString = '这是' + index;
}
console.timeEnd();
setTimeout(() => {
console.log('2秒计时器');
}, 2000);
结果是什么呢?可以思考一下,再看下面的结果:
// default: 5030.498779296875ms
// 3秒计时器(稍慢)
// 2秒计时器(2s后)
运行原理是什么呢?
当JS运行到第一个计时器时,将其交给浏览器去计时,然后开始执行同步操作(for循环),在for循环进行3秒后(完成需要5s),浏览器将第一个计时器计时完成,然后将其返回到事件队列中等待,等到for循环执行完,然后执行被存到事件队列中的第一个计时器。然后代码走到第二个计时器时,再交给浏览器去执行,现在js线程是空闲状态,等到浏览器计时结束后,浏览器将其推送到js线程中。
以上就是我们看到的打印结果。
有的人可能就很好奇了,那如果在同步任务执行完,第一个计时器还没计时结束,那结果是什么?大家可以自己写写看看结果是什么
总的来说,当我们遇到计时器时,就将其推送到浏览器去计时,当计时结束就将其推送到当前task任务之后,当前task任务结束后即开始执行之前被推送进来的计时器。
小测一下,下面代码打印出的结果是什么:
setTimeout(() => {
console.log('3秒计时器');
}, 3000);
setTimeout(() => {
console.log('1秒计时器');
}, 1000);
console.time();
for (let index = 0; index < 10000000; index++) {
index.toString = '这里是1' + index;
// console.log()
}
console.timeEnd();
setTimeout(() => {
console.log('2秒计时器');
}, 2000);
如果你认真看过本文,那应该是很easy的事情:
// default: 5192.325657765ms
// 1秒计时器
// 3秒计时器
// 2秒计时器(2s后)