当用户在网页上操作时,我们需要通过JavaScript代码来响应用户的事件,例如点击、滚动、输入等等。但是由于JavaScript是单线程执行的,如果在响应事件的同时还要执行许多其他的代码,就会导致页面出现卡顿、响应迟缓的问题。为了解决这个问题,JavaScript引入了事件循环机制。
什么是事件循环
事件循环是JavaScript引擎实现多任务的基础,在执行JavaScript代码时,主线程会不断地从任务队列中取出任务并执行。如果任务队列中没有任务,主线程就会等待任务出现。
任务队列分为两种类型:宏任务(macro-task)和微任务(micro-task)。宏任务包括DOM事件、定时器任务、网络请求等,而微任务主要有Promise、MutationObserver等。
当主线程执行完一个宏任务后,会立即去检查是否有微任务需要执行,如果有,则依次取出并执行这些微任务,直到所有微任务都被执行完毕,才会再次取出下一个宏任务执行。
事件循环的示例
示例一
假设我们有如下代码:
console.log('Task1');
setTimeout(() => {
console.log('Task2');
}, 0);
Promise.resolve().then(() => {
console.log('Task3');
});
console.log('Task4');
按照事件循环的执行顺序,程序会先执行主线程中的当前任务(即Task1),然后检查微任务队列,发现有一个Promise的回调函数(即Task3),于是执行Promise的回调函数。接着,再次检查有没有微任务,此时微任务队列为空。
由于我们设置了一个0ms的定时器(即Task2),因此Task2会被加入宏任务队列,此时主线程中没有其他任务,于是会去执行Task2。执行完Task2后,又会检查微任务队列,发现队列中为空,于是继续检查宏任务队列。
最后,剩下的一条任务是Task4,于是会被主线程执行。整个程序的输出结果如下:
Task1
Task4
Task3
Task2
可以看到,虽然定时器设置的是0ms,但是定时器回调函数Task2并没有立即执行,而是在所有微任务都被执行完毕后才被执行。
示例二
再看一个复杂一点的例子:
console.log('Task1');
setTimeout(() => {
console.log('Task2');
}, 0);
Promise.resolve().then(() => {
console.log('Task3');
Promise.resolve().then(() => {
console.log('Task4');
});
}).then(() => {
console.log('Task5');
});
console.log('Task6');
这个例子中,我们多了一层Promise,并在Promise的回调函数中嵌套了一个Promise的回调函数。
主线程首先执行Task1,然后将两个Promise的回调函数(Task3和Task4)放入微任务队列中。接着检查宏任务队列,发现有一个定时器,将Task2放入宏任务队列中。
执行完毕后,按照事件循环的顺序依次执行微任务队列中的Task3和Task4。注意,Task4是Task3的嵌套回调函数,因此在Task3执行完毕后才能执行Task4。
执行完毕微任务队列后,检查微任务队列,发现还有一个Task5,于是立刻执行该任务。
最后,主线程执行Task6。整个程序的输出结果如下:
Task1
Task6
Task3
Task4
Task5
Task2
总结
通过这两个例子,我们可以看到JavaScript引擎通过任务队列和事件循环机制实现了多任务的执行。在编写JavaScript代码的时候,我们需要注意事件循环的规则,合理地使用微任务和宏任务,避免出现卡顿、响应迟缓的情况。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:JavaScript之事件循环案例讲解 - Python技术站