首先我们需要了解 JS 代码的执行分为 同步任务和异步任务,对于同步任务会放到执行栈中执行,异步任务放到任务队列中执行,这里同时涉及到事件循环机制。
先看下面的代码,然后我们对代码进行流程的讲解:
console.log(1)
setTimeout(() => { console.log(2) }, 0)
setTimeout(() => { console.log(3) }, 500)
setTimeout(() => { console.log(4) }, 1000)
console.log(5)
很明显输出结果:1 5 2 3 4
然后大家看我给的画的大致流程图,然后下面我会给出讲解
执行流程:
- 首先肯定会先执行同步任务,正常执行 console.log(1)、console.log(5)
- 然后执行异步任务,setTimeout 会在给定的延时过后,将其中的程序放到任务队列中,首先是将 console.log(2) 放入任务队列
- 事件循环机制会持续运行,当执行栈为空时,事件循环会定时检查任务队列,将当前的代码放到执行栈中进行执行,此时输出结果为:1 5 2
- 500ms结束后,setTimeout 会将 console.log(3) 放到任务队列,如果此时执行栈没有任务,则事件循环会将 console.log(3) 放到执行栈执行
- 依次循环......
注意:setTimeout(() => { console.log(4) }, 1000) 不是在1s之后执行是因为:虽然在1s之后我们将console.log(4)放到任务队列,但是此时并不一定会立即执行,如果执行栈中程序较多,会等待执行栈执行完,才会通过事件循环机制将任务队列中的任务放到执行栈中执行,这样就会产生时延。同时如果在任务队列中,console.log(4)之前还有其他代码等待执行,此时同样会产生时延。所以setTimeout在大多数情况下是不会准时执行的。
如果面试问到你,如何让它尽量准时执行呢?
今天看了一个模拟面试,老师给的解决办法是算出大致延时的时间,将 setTimeout 第二个参数时间进行提前,如果正常情况下我们执行 setTimeout 为 1s,但是实际执行 1.2s ,我们便可在测试的过程中将 1s 改为 0.8s。当然这样只是给了一个思路,如果大家有其他的方法,请在评论区交流吧!
附:事件循环机制
事件循环是一个持续运行的机制,用于处理 JavaScript 中的异步任务。它的主要作用是不断地从任务队列中取出任务,并将其放入执行栈中执行。
当执行栈为空时,事件循环会检查任务队列。如果任务队列中有任务,事件循环会选择一个任务(通常是先进先出的方式,除非使用了优先级队列),并将其放入执行栈中执行。这个过程会重复进行,直到任务队列中没有任务。
通过这种方式,事件循环实现了非阻塞的异步任务处理。它会持续地从任务队列中获取任务,而不是等待任务的完成或阻塞执行。这使得 JavaScript 可以同时处理多个异步任务,并保持其单线程的特性。
需要注意的是,事件循环不会主动去查找任务队列中要执行的任务,而是在特定的时机(例如当前宏任务执行完毕)检查任务队列。当执行栈为空时,事件循环会启动并开始执行任务队列中的任务。这个过程一直循环进行,直到没有任务需要执行。
下面是事件循环中宏任务和微任务的大致执行顺序:
推荐一下别的博主优秀的博文: