JavaScript是一种单线程编程语言,一次只能做一件事。但是遇到异步事件时,js线程不会被阻塞,会继续执行。这是因为JS有一个事件循环机制。本文将为您详细讲解这一机制,有需要的可以参考。
目录
前言1。JavaScript是单线程2。同步和异步3。事件循环
前言
我们知道JavaScript是单线程编程语言,同一时间只能做一件事,按顺序处理事件。但是遇到异步事件时,js线程不会被阻塞,会继续执行。为什么?本文总结了js的事件循环机制。
1、JavaScript是单线程的
JavaScript是单线程编程语言,只有一个调用栈,这就决定了它一次只能做一件事。代码执行时,不同函数的执行上下文被压入执行栈,以保证代码的有序执行。在执行同步代码时,如果遇到异步事件,js引擎不会一直等待其返回结果,而是将事件挂起,继续执行执行栈中的其他任务。因此,JS是一种非阻塞、异步、并发的编程语言。
2、同步和异步
同步和异步的关系类似于我们在餐厅排队吃饭的时候,大家都要一个一个的排队买菜,而且在这个过程中非常无聊。这时候我们就可以排队玩手机了,买菜也不用多久。这个排队过程在JS中是同步操作,玩手机就像异步操作。同步和异步的区别在于排队领饭和玩手机这两个任务的执行顺序不同。
同步:指的是在主线程上排队等待执行的任务。只有前一个任务完成后,才能执行后一个任务。可以理解为,程序在执行一个函数或方法后,通过等待系统的返回值或消息来阻塞,只有收到返回值或消息后,才会执行其他命令。
异步:意味着异步任务可以在进入主线程之前执行。在执行一个函数或方法后,你不必以阻碍的方式等待返回值或消息,只需将一个异步进程委托给系统。然后,当系统收到返回值或消息时,系统会自动触发委托异步流程,从而完成一个完整的流程。
console . log(1);
setTimeout(()={
console . log(2);
}, 0);
setTimeout(()={
console . log(3);
}, 0);
setTimeout(()={
console . log(4);
}, 0);
console . log(5);
上面的代码将打印1 " 5 " 2 " 3 " 4。为什么会是这样的结果?让我们来看看事件循环。
3、事件循环
循环过程可以简单地描述为:
a、将函数放入栈中,当异步任务在栈中执行时,抛出给WebAPIs,然后执行同步任务,直到栈为空;
B.在此期间,WebAPIs完成此事件,并将回调函数放在CallbackQueue(任务队列)中等待;
c、当执行栈为空时,事件循环将回调队列中的任务放入栈中,并返回步骤1。
事件循环是让JavaScript单线程化的核心机制,绝对不阻塞。它也是JavaScript并发模型的基础,是协调各种事件、用户交互、脚本执行、UI呈现、网络请求等的机制。在执行和协调各种任务时,事件循环维护自己的事件队列。
事件队列是存储要执行的任务的队列,其中任务按照严格的时间顺序执行。队列头的任务将首先执行,而队列尾的任务将最后执行。队列一次只执行一个任务。任务完成后,将执行下一个任务。一项任务开始完成后,不会被其他任务打断。执行堆栈是一个运行容器,类似于函数调用堆栈。当执行堆栈为空时,JS引擎检查事件队列。如果没有,事件队列将第一个任务推入执行堆栈运行。
任务队列:在JavaScript中,异步任务分为两种,一种是宏任务,也叫任务,另一种是微任务:
宏任务的例子有很多,包括创建主文档对象、解析HTML、执行主线(或全局)JavaScript代码、改变当前URL和各种事件,比如页面加载、输入、网络事件和定时器事件。从浏览器的角度来看,宏任务代表了离散和独立的工作单元。运行任务后,浏览器可以继续其他调度,比如重新呈现页面的UI或者执行垃圾回收。
微任务是更小的任务。微任务更新应用程序的状态,但它必须在浏览器任务继续执行其他任务之前执行,包括重新呈现页面的UI。微任务的例子有promise回调函数、DOM改变等。微任务需要以异步的方式尽可能快地执行,同时不能产生新的微任务。微任务使我们能够在重新渲染UI之前执行指定的行为,避免不必要的UI重绘,重绘会使应用程序的状态不连续。
当当前执行栈中的事件结束后,js engine会先判断微任务对列中是否有可以执行的任务,如果有,则将微任务组长的事件推入栈中执行。当列中的所有任务都由微任务执行时,通过宏任务判断列中的任务。每次宏任务结束,都会判断微任务队列是否生成新的任务。如果存在,则先执行微任务,否则依次执行宏任务。
事件通常需要至少两个任务队列:宏任务队列和微任务队列。两个队列同时只执行一项任务。
console.log(脚本开始);
setTimeout(function () {
console . log( settime out );
}, 0);
Promise.resolve()。then(function () {
console . log( promise 1 );
})。then(function () {
console . log( promise 2 );
});
console.log(脚本结束);
根据以上内容,分析执行步骤:
1.宏任务:执行整个代码(相当于脚本中的代码):
输出:脚本开始符合setTimeout并加入宏任务队列;当前宏任务队列(setTimeout)符合承诺,加入微任务;当前微任务队列(promise1)输出:脚本结束。
2.微任务:执行微任务队列(promise1)
输出:promises 1,然后生成一个微任务,并添加到微任务队列中。然后执行当前的微任务队列(promise2),输出promise2执行渲染操作,更新界面。宏:执行setTimeout输出:setTimeout
注意:新承诺中的代码(.),也是同步代码,会立即执行。只有之后的代码才是异步执行的代码,这是一个微任务。
console.log(脚本开始);
setTimeout(function () {
console . log(“time out 1”);
}, 10);
新承诺((解决)={
console . log( promise 1 );
resolve();
settime out(()=console . log( time out 2 ),10);
}).then(function () {
console . log(“then 1”);
});
console.log(脚本结束);
步骤分析:
当前任务队列:微任务:[],宏任务:[脚本]
宏任务:
输出:脚本start遇到timeout1,加入宏任务遇到Promise,输出promise1,直接解析,然后加入微任务,遇到timeout2,加入宏任务。首先执行输出脚本结束宏任务。当前任务队列:微任务[then1],宏任务[time out 1,timeout2]
微任务:
执行then1,输出then1微任务队列清空当前任务队列:微任务[],宏任务[time out 1,timeout2]
宏任务:
输出超时1输出超时2当前任务队列:微任务[],宏任务[time out 2]
微任务:
清空跳过当前任务队列:微任务[],宏任务[timeou2]
宏任务:
输出超时2
注意:async和await实际上是Generator和Promise的语法糖。异步函数和普通函数没什么区别。只是说明这个函数中有异步操作方法,返回一个Promise对象。
异步函数async1() {
console . log( async 1 start );
await async 2();
console . log( async 1 end );
}
//写承诺
异步函数async1() {
console . log( async 1 start );
Promise.resolve(async2())。然后(()=console . log( async 1 end ));
}
下面的例子:
异步函数async1() {
console . log( async 1 start );
await async 2();
console . log( async 1 end );
}
异步函数async2() {
console . log(“async 2”);
}
async 1();
setTimeout(()={
console.log(timeout )。
}, 0);
新承诺(功能(解决){
console . log( promise 1 );
resolve();
}).then(function () {
console . log( promise 2 );
});
console.log(脚本结束);
步骤分析:
当前任务队列:宏任务:[脚本],微任务:[]
宏任务:
输出:async1 start遇到async2,输出:async2,然后(async1 end)加入微任务。当遇到setTimeout时,它被添加到宏任务中。满足Promise,输出:promise1,直接解析,添加then(promise2)到微任务输出:脚本结束当前任务队列:微任务[promise2,async1 end],宏任务[timeout]
微任务:
Output: promise2promise2出列output: async1 endasync1 end出列微任务队列清空当前任务队列:微任务[],宏任务[超时]
宏任务:
输出:timeouttimeout出队,宏任务被清除。
关于详细解释JS中事件循环机制的这篇文章到此为止。关于JS事件循环机制的更多信息,请搜索我们之前的文章或者继续浏览下面的相关文章。希望大家以后能多多支持我们!