setTimeout 详解

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

发布于 2019-10-08 21:01