详解JavaScript事件循环
作者:Nooo_47
JavaScript事件循环是一种机制,用于处理异步事件和回调函数。它是JavaScript运行时环境的一部分,负责管理事件队列和调用栈。
事件循环的基本原理是事件循环的核心是一个事件队列,所有的事件都被放入这个队列中,然后按照顺序依次执行。如果队列为空,JavaScript会等待新的任务加入队列。当JavaScript代码执行时,所有同步任务都会被立即执行,而异步任务则会被放入事件队列中。
当所有同步任务执行完毕后,事件循环会从事件队列中取出一个任务,并将其放入调用栈中执行。当该任务执行完毕后,事件循环会再次从事件队列中取出下一个任务,并重复这个过程。
一、事件循环的执行过程
1、执行同步代码,直到遇到第一个异步事件(如setTimeout、setInterval、Promise等)。
2、将异步事件放入事件队列中,并继续执行同步代码。
3、当所有同步代码执行完毕后,JavaScript引擎会检查事件队列中是否有事件需要执行。
4、如果事件队列中有事件需要执行,JavaScript引擎会将第一个事件取出来,并执行对应的回调函数。
5、执行完回调函数后,JavaScript引擎会再次检查事件队列中是否有事件需要执行,如果有则重复步骤4,否则继续等待新的事件加入事件队列。
需要注意的是,事件循环是单线程的,也就是说JavaScript引擎在同一时间只能执行一个任务。
因此,如果一个任务执行时间过长,会阻塞事件循环,导致其他任务无法执行。为了避免这种情况,可以将长时间的任务拆分成多个小任务,使用setTimeout或setInterval分批执行。
另外,Promise也是基于事件循环的机制实现的。当Promise状态发生改变时,会将对应的回调函数放入微任务队列中,等待当前任务执行完毕后立即执行。因此,Promise的回调函数总是在当前任务执行完毕后立即执行,而不会被放入事件队列中等待执行。
二、事件循环进阶用法
1、Promise
:Promise是一种异步编程的解决方案,它可以将异步操作转化为同步操作的形式,使得代码更加简洁易懂。Promise的基本原理是将异步操作封装成一个Promise对象,通过then方法来处理异步操作的结果。
2、async/await
:async/await是ES2017引入的一种异步编程的解决方案,它可以让异步代码看起来像同步代码,使得代码更加易读易懂。async/await的基本原理是使用async关键字定义一个异步函数,然后在函数内部使用await关键字来等待异步操作的结果。
3、定时器:JavaScript中有两种定时器,分别是setTimeout和setInterval。setTimeout用于在指定的时间后执行一次任务,而setInterval则用于每隔一段时间执行一次任务。这两种定时器都是异步任务,会被放入任务队列中等待执行。
三、JavaScript任务类型
JavaScript中的任务按照不同纬度进行区分主要分为同步任务和异步任务、宏任务和微任务。
3.1 同步任务&异步任务
1、 同步任务按照代码顺序执行的任务。
2、 异步任务是在将来某个时间点执行的任务。异步任务通常是由事件触发器(如鼠标点击、网络请求等)生成的。 当一个异步任务被触发时,它会被放入任务队列中等待执行。
JavaScript事件循环的执行顺序可以用以下伪代码表示:
while (queue.waitForMessage()) { queue.processNextMessage(); }
在这个伪代码中,queue.waitForMessage()
表示等待队列中有待执行的任务,queue.processNextMessage()
表示取出队列中的下一个任务并执行。
需要注意的是,JavaScript事件循环的执行顺序是不可中断的。这意味着当一个任务正在执行时,其他任务必须等待,直到当前任务完成。因此,如果一个任务执行时间过长,会导致其他任务无法及时执行,从而影响应用程序的性能和响应速度。
3.2 宏任务&微任务
1、宏任务包括setTimeout、setInterval、I/O操作等。
2、微任务包括Promise、MutationObserver等。
当事件循环从事件队列中取出一个任务时,它会先执行所有微任务,然后再执行一个宏任务。这个过程会一直重复,直到事件队列中的所有任务都被执行完毕。
下面是一个例子:
console.log('1'); setTimeout(function() { console.log('2'); Promise.resolve().then(function() { console.log('3'); }); }, 0); Promise.resolve().then(function() { console.log('4'); }); console.log('5'); // 输出结果为: 1 5 4 2 3
执行顺序:
- 执行第一个
console.log
,输出1。 - 执行
setTimeout
,将其回调函数放入宏任务队列中。 - 执行
Promise.resolve().then
,将其回调函数放入微任务队列中。 - 执行第二个
console.log
,输出5。 - 当前任务执行结束,执行微任务队列中的所有任务,输出4。
- 执行宏任务队列中的第一个任务,即
setTimeout
的回调函数,输出2。 - 执行
Promise.resolve().then
的回调函数,输出3。
需要注意的是,微任务和宏任务的执行顺序是固定的,即先执行所有微任务,再执行宏任务。因此,如果在一个宏任务中产生了新的微任务,那么这些微任务会在下一个宏任务执行之前执行。
下面是一个例子:
console.log('1'); setTimeout(function() { console.log('2'); Promise.resolve().then(function() { console.log('3'); }); }, 0); Promise.resolve().then(function() { console.log('4'); setTimeout(function() { console.log('5'); }, 0); }); console.log('6'); // 输出结果为:1 6 4 2 3 5
执行顺序:
- 执行第一个
console.log
,输出1。 - 执行
setTimeout
,将其回调函数放入宏任务队列中。 - 执行
Promise.resolve().then
,将其回调函数放入微任务队列中。 - 执行第三个
console.log
,输出6。 - 当前任务执行结束,执行微任务队列中的所有任务,输出4。
- 执行宏任务队列中的第一个任务,即
setTimeout
的回调函数,输出2。 - 执行
Promise.resolve().then
的回调函数,将setTimeout的回调函数放入宏任务队列中。 - 当前任务执行结束,执行微任务队列中的所有任务,输出3。
- 执行宏任务队列中的第一个任务,即
setTimeout
的回调函数,输出5。
到此这篇关于详解JavaScript事件循环的文章就介绍到这了,更多相关JavaScript事件循环内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!