javascript - event loop

javascript,区别于后台,就是javascript是单线程的。单线程作到不阻塞,起到做用的其实就是咱们常说的异步。javascript

运行时概念

首先,咱们来理解一下javascript的几个概念java

  • 堆(heap)
  • 栈(stack)
  • 任务队列(queue),这里又分为宏任务 & 微任务

浏览器的event loop

当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的event loop

node代码也是javascript,解析javascript的是V8引擎。异步i/o采用的是libuv。

node的event loop,有六个事件,依次循环

  • poll:获取新的i/o事件,大部分事件都在这里执行
  • check:执行setImmediate的回调
  • close:执行socket的close事件回调
  • timers:执行setTimeout、setInterval的回调
  • i/o:处理上一轮循环中少许未执行的i/o回调
  • idle,prepare:node内部使用

咱们来看下代码的运行状况

结果是这样的:同步任务 - 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')
	})
})
复制代码

process.nextTick()

在上面的例子中,咱们会发现,nextTick的执行老是比微任务要快。

在node中,nextTick实际上是独立于event loop以外的,nextTick拥有本身的任务队列,event loop,执行完一个阶段以后,就会将nextTick中的全部任务先清空,再执行微任务。

写在最后

浏览器的event loop 与node的event loop仍是有稍许不一样,不过大体的概念是差很少的,只要弄懂其中的关系以后,代码中出现的问题就迎刃而解。

相关文章
相关标签/搜索