这一次,彻底弄懂JS执行机制(Event Loop)

本文解析了JavaScript为何采用单线程模型及其带来的挑战,介绍了同步与异步任务的执行机制,包括宏任务与微任务的区别,以及浏览器与Node.js环境中事件循环的工作原理。

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环境下的一些区别。   



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值