现阶段来说,JS 的主要运行环境包含两个:
- 浏览器
- Node.js
事件循环机制
1、分类分为两种:
- 浏览器内部的事件循环机制。
- Node.js内部的事件循环机制。
2、什么是事件循环?
- JS是单线程,所以就会导致代码的阻塞,为了防止这种情况,我们会把代码分为同步和异步
- 同步代码是交给JS引擎执行,异步代码交给宿主环境(浏览器/Node,他们是支持多线程的)
- 同步代码放入执行栈中执行,异步代码等待时机成熟送入任务队列排队(自我理解时机成熟是比如定时器时间到了或者点击事件被触发了)
- 执行栈同步代码执行完毕之后,会去任务队列看是否有异步任务,有就送到执行队列,这样反复循环查看的过程,我们称为事件循环
3、执行顺序:
●首先执行同步代码,它是在执行栈里面去执行的
●然后是微任务中的异步代码去微任务队列排队,先进先出
●宏任务中的异步代码去宏任务排队,先进先出
●当执行栈里面的代码执行完了之后,就会去看微任务队列,利用事件循环、先进先出将微任务推到执行栈全部执行完之后,再去看宏任务队列,利用事件循环,先进先出将宏任务推到执行栈执行
4、异步任务有哪些
在浏览器端,我们通常可能遇见的异步操作有:
- 计时器
- 事件监听
- 异步请求
到了 Node.js 方面,跟用户有关的鼠标和键盘操作的用户事件监听虽然没了,却多了很多服务端特有的异步任务。
- 文件读写相关的 I/O 任务
- 线程相关的 process.nextTick
- 服务相关的 server.close,socket.on('close', ...)
5、node.js事件循环机制相比于浏览器内部的事件循环机制的区别?
首先,它重新给宏任务分了类。
其次,它添加了任务阶段的概念。
相当于在浏览器里,宏任务们不分组,只要是宏任务,大家都待在一起,执行时按照入队顺序。到了 Node.js 里,不同的宏任务被分了组,只有前一组执行结束,后一组才有执行的机会。
举一个具体的例子吧。
计时器队列内有 3 个待执行回调,系统任务队列内有 5 个待执行回调,I/O 队列内有 2 个,那么等事件循环函数开始执行时,它不会把这些任务混杂在一起,先后去执行,它会先去执行计时器队列内的回调,等计时器队列的三个回调执行结束,它再去执行系统任务回调,之后来到 I/O。
setTimeout(() => {
console.log('宏任务 timers 阶段:timeout1')
Promise.resolve().then(function () {
console.log('微任务:promise1');
});
}, 0);
setTimeout(() => {
console.log('宏任务 timers 阶段:timeout2')
Promise.resolve().then(function () {
console.log('微任务:promise2');
});
}, 0);
于是在浏览器,这段代码的输出依次是:
- 宏任务 timers 阶段:timeout1
- 微任务:promise1
- 宏任务 timers 阶段:timeout2
- 微任务:promise2
但到了 Node.js 11 之前的环境里,情况变得不太相同,在这里,这段代码的输出内容是:
- 宏任务 timers 阶段:timeout1
- 宏任务 timers 阶段:timeout2
- 微任务:promise1
- 微任务:promise2
这是因为,在 Node.js 11 之前的环境里,事件循环会先将一个阶段的所有任务先清空,再去检查微任务队列的内容,在这里,代码的执行次序如下:
- 同步代码执行结束后,timers 阶段多出两条计时任务。
- 时间到了,两条计时任务的回调函数,先后被添加进 timers 阶段的待执行回调队列中。
- 事件循环来到 timers 阶段,发现任务队列不为空,于是,先执行第一条计时任务的回调函数:输出 timeout1,将输出 promise1 添加进微任务队列;再执行第二个计时任务的回调函数:输出 timeout2,将输出 promise2 添加进微任务队列。
- timers 阶段的回调队列被清空,往下个阶段进发之前,先去检查微任务队列,发现微任务队列不为空,按照顺序依次执行,promise1 和 promise2 也相继被输出。
什么是同步任务?什么是异步任务?
同步任务是按照代码顺序执行的任务,执行结果会同步返回,而异步任务是不按代码顺序执行的任务,执行结果不会立即返回,而是通过回调函数或者Promise等方式返回。
什么是微任务?什么是宏任务?它们有什么区别?
微任务和宏任务都是异步任务,宏任务是指在主线程之外的任务,比如定时器、事件回调等,而微任务是指在当前任务之后立即执行的任务,比如Promise的then方法和MutationObserver的回调函数等。宏任务和微任务的区别在于宏任务在任务队列中排队,每次只执行一个任务,而微任务在任务队列之后执行,每次执行所有微任务。
- 宏任务:setInterval、setTimeout、setImmediate、Ajax、DOM事件、异步函数、I/O、UI渲染等。
- 微任务:process.nextTick、MutationObserver、Promise.then catch finally
简单来说,微任务队列的优先级高于宏任务队列。
在微任务队列不为空的情况下,JS 引擎优先执行微任务队列的内容,等微任务队列空了,它才会去执行宏任务队列的内容。