js单线程
从接触js开始,我们便知道js是单线程,大家有没有想过为什么js没有被设计成多线程呢?因为多线程之间会共享运行资源,浏览器端的js会操作dom,多个线程肯定会带来同步的问题,所以js核心是单线程的,来避免这个麻烦。
堆(heap)、栈(stack)、队列(queue)
- 堆(heap)
在js运行时,主要是存放对象的空间。
- 栈(stack)
栈中主要存放的是,各种函数执行(dom操作,ajax操作)、声明的一些变量。我们这里说的栈,代码的调用栈,是一个先进后出的结构。
- 队列(queue)
存放的是各种异步、回调函数,是一个先进先出的队列结构。
js任务
单线程意味着js任务需要排队,如果前一个任务出现大量的耗时操作,后面的任务就得不到执行,这样就会导致页面出现“假死”,于是聪明的程序猿将js任务分为了两类,同步任务和异步任务。
- 同步任务
在主线程排队执行的任务,前一个任务执行完成后,再执行下一个任务,实现函数的层层调用。
- 异步任务
异步任务会被主线程挂起啦,不会进入主线程,而是进入任务队列,并且必须指定回调函数,只有消息队列通知主线程,并且执行栈为空时,任务队列的任务才会进入执行栈执行。
- 宏任务&微任务
异步任务在执行的时候,都是按照时间先后顺序执行的吗?NO!事实上异步任务分为宏任务和微任务。当执行栈中都执行完后,会优先清空微任务队列,当微任务清空后,才会执行宏任务队列中的函数。
宏任务:setTimeout,、setInterval、setImmediate
微任务:then、MutationObserver、MessageChannel、nextTick
浏览器中的Event Loop
同步和异步任务分别进入不同的执行“场所”,同步的进入执行栈,异步的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入到Queue中(微任务移入到微任务队列,宏任务移入到宏任务队列)。当“执行栈”中的所有同步任务执行完毕,系统会优先读取“微任务,按先进先出的顺序,取出对应的任务,进入执行栈,开始执行;微任务清空后再读取“宏任务,按先进先出的顺序,取出对应的任务,进入执行栈,开始执行。上述过程会不断重复,就是常说当Event Loop(事件循环)。
举个?
console.log(1)
setTimeout(() => {
console.log('setTimeout')
})
Promise.resolve().then(() => {
console.log('then')
}) 复制代码
先把同步代码放到执行栈执行,打印出1
then放到微任务队列,setTimeout放到宏任务队列
先清空微任务 打印出then
然后取出宏任务队列函数执行打印出setTimeout
,
打印结果是:1、then、setTimeout
Node系统中的Event Loop
nodejs也是单线程的,但是它的运行机制不同于浏览器。
nodejs的Event Loop是基于libuv,libuv已经对Event Loop作出了实现。
当nodejs启动时,会初始化Event Loop,每一个Event Loop都会包含如下六个循环阶段,并按顺序执行。
- timers:执行setTimeout,setInterval预定的callback;
- I/O callbacks:处理网络,流,tcp的错误callback
- idle,prepare:node内部使用
- poll:执行poll中的i/o队列检查定时器是否到时(fs的i/o操作)
- check:存放setImmeditate
- close cllbacks:关闭的回调,如socket.on('close')
Node中的Event Loop 同步的进入执行栈,异步任务分别进入各自对用的循环阶段,等待执行。当同步执行栈执行会切换到队列执行阶段,这时候会先去清空微任务(有阶段切换就会清空微任务),然后再从上到下执行上面当六个循环阶段。
?思考一下,如下代码在node中执行顺序
setTimeout(() => {
console.log('setTimeout')
})
setImmediate(() => {
console.log('setImmediate')
}) 复制代码
是不是setTimeout先打印出来呢?因为setTimeout在最上面执行,而setImmediate属于check阶段的,在后面。 但是,结果告诉我们,不一定。有木有很震惊?,说好的按顺序执行呢,那为什么不一定呢,其实是,node启动需要时间,当启动完成后,timers可能到时间了,如果到时间了,就会执行timers,但如果没到时间,就会跳过timers往下执行,这样就会等到下次循环当时候才会执行timers对应当函数。so,它们谁先执行,这取决于node的执行时间,明白了吧。
?看看下代码,它会怎么执行呢?
setTimeout(() => {
console.log('setTimeout1')
process.nextTick(() => {
console.log('nextTick1')
})
})
process.nextTick(() => {
console.log('nextTick1')
setTimeout(() => {
console.log('setTimeout2')
})
}) 复制代码
node在执行的时候 ,不是取出一个 放到栈里执行,而是把队列里到达时间的全部清空,再执行微任务。
所以正确答案是:nextTick1、、setTimeout1、setTimeout2、nextTick1
。
以上,就是事件环(Event Loop)在浏览器和node环境下的一些区别。