javascript,区别于后台,就是javascript是单线程的。单线程作到不阻塞,起到做用的其实就是咱们常说的异步。javascript
首先,咱们来理解一下javascript的几个概念java
当javascript运行的时候,首先,代码会进入执行栈,变量之类的会存储在堆中,而任务队列存储的就是javascript中的异步任务。node
咱们来看下下面的例子,首先,script代码会进入执行栈,而后执行同步代码,接着将异步任务放到任务队列中。promise
先执行同步代码,打印1,2,Promise(promise中的代码是同步执行的),3。浏览器
接着将异步任务放入任务队列中,promise回调放入微任务中,setTimeout回调放到宏任务中。异步
在event loop中,执行栈的代码执行完以后,在微任务队列取一个事件放到执行栈中执行,当微任务队列为空时,就从宏任务中取一个事件放到执行栈中执行,如此反复循环。socket
console.log(1)
setTimeout(() => {
console.log('setTimeout')
})
console.log(2)
new Promise((resolve, reject) => {
console.log('Promise')
resolve()
}).then(() => {
console.log('then')
})
console.log(3)
复制代码
咱们修改一下代码,咱们在promise的回调中又加了一个promise。oop
其余不变,当执行第一个promise的回调时,同步执行第二个promise,这个没有问题,此时,把第二个promise的回调加入到微任务中。ui
在下一次event loop中,先查看微任务队列,因而执行第二个promise的回调,打印了then1。spa
最后,微任务队列清空了,因而查看宏任务,执行setTimeout的回调。
console.log(1)
setTimeout(() => {
console.log('setTimeout')
})
console.log(2)
new Promise((resolve, reject) => {
console.log('Promise')
resolve()
}).then(() => {
console.log('then')
new Promise((resolve1, reject1) => {
console.log('Promise1')
resolve1()
}).then(() => {
console.log('then1')
})
})
console.log(3)
复制代码
咱们前面的例子其实都是当即执行的代码,当发送http请求时,请求先挂起,当请求结果回来时,再将请求回调加入到任务队列中。
node代码也是javascript,解析javascript的是V8引擎。异步i/o采用的是libuv。
node的event loop,有六个事件,依次循环
咱们来看下代码的运行状况
结果是这样的:同步任务 - nextTick - 微任务 - 宏任务 - setImmediate
setTimeout(() => {
console.log('setTimeout')
})
new Promise((resolve, reject) => {
console.log('Promise')
resolve()
}).then(() => {
console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
process.nextTick(() => {
console.log('nextTick')
})
复制代码
当咱们把代码嵌到异步i/o里面呢
结果是这样的:同步任务 - nextTick - 微任务 - setImmediate -宏任务
与刚刚不一样的是,代码放到异步i/o里面,执行完poll以后,执行的是check,因此setImmediate会在宏任务以前
setTimeout(() => {
console.log('setTimeout')
setTimeout(() => {
console.log('setTimeout1')
})
new Promise((resolve, reject) => {
console.log('Promise')
resolve()
}).then(() => {
console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
process.nextTick(() => {
console.log('nextTick')
})
})
复制代码
最后,咱们来看一下node中宏任务与微任务的顺序
结果是先把宏任务队列中的回调所有执行完毕,接着执行所有nextTick,最后执行全部的微任务。
这个就是跟浏览器不一样了,浏览器是执行完一个任务以后,先执行全部微任务,而后再执行下一个宏任务。
setTimeout(() => {
console.log('setTimeout')
Promise.resolve().then(() => {
console.log('then')
})
process.nextTick(() => {
console.log('nextTick')
})
})
setTimeout(() => {
console.log('setTimeout2')
Promise.resolve().then(() => {
console.log('then2')
})
process.nextTick(() => {
console.log('nextTick2')
})
})
复制代码
在上面的例子中,咱们会发现,nextTick的执行老是比微任务要快。
在node中,nextTick实际上是独立于event loop以外的,nextTick拥有本身的任务队列,event loop,执行完一个阶段以后,就会将nextTick中的全部任务先清空,再执行微任务。
浏览器的event loop 与node的event loop仍是有稍许不一样,不过大体的概念是差很少的,只要弄懂其中的关系以后,代码中出现的问题就迎刃而解。