事件循环

23 篇文章 0 订阅
4 篇文章 0 订阅

JavaScript一门是单线程的脚本语言,为什么说它是单线程的呢?
作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它是单线程,否则会带来很复杂的同步问题。举个例子,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程。但是,H5中的Web Works可以开启分线程。
Event Loop即事件循环,是指浏览器或Node的一种解决JavaScript单线程运行时不会阻塞的一种机制,是实现异步的一种方法。

在js中,任务被分为两类:

  • 宏任务(任务)MacroTask
    script全部代码、setTimeout、setInterval、setImmediate(只有IE10支持)、I/O、UI Rendering。
  • 微任务 MicroTask
    Process.nextTick(Node独有)、Promise.then()、MutationObserver(监听dom节点更新完毕)

浏览器的Event Loop
执行过程:

  • 首先执行script,script被称为全局任务,也属于macrotask;
  • 当macrotask执行完以下,执行所有的微任务;
  • 微任务全部执行完,再取任务队列中的一个宏任务执行。
    在这里插入图片描述
    看下面这个例子:
setTimeout(function(){
    console.log(1) //宏任务
},0)
new Promise(function executor(resolve) {
  console.log(2)  //同步任务
  for(var j = 0;j<100;j++){
    j=99&&resolve()
  }
  console.log(3)  //同步任务
}).then(function(){
    console.log(4)  //微任务
})
console.log(5)  //同步任务

// 2 3 5 4 1
setTimeout(() => {
  console.log('A');
}, 0);
var obj = {
  func: function() {
    setTimeout(function() {
      console.log('B');
    }, 0);
    return new Promise(function(resolve) {
      console.log('C');
      resolve();
    });
  },
};
obj.func().then(function() {
  console.log('D');
});
console.log('E');

//  C D E A B  
  1. 第一个 setTimeout 放到宏任务队列,此时宏任务队列为 [‘A’]

  2. 接着执行 obj 的 func 方法,将 setTimeout 放到宏任务队列,此时宏任务队列为 [‘A’, ‘B’]

  3. 函数返回一个 Promise,因为这是一个同步操作,所以先打印出 ‘C’

  4. 接着将 then 放到微任务队列,此时微任务队列为 [‘D’]

  5. 接着执行同步任务 console.log(‘E’);,打印出 ‘E’

  6. 因为微任务优先执行,所以先输出 ‘D’

  7. 最后依次输出 ‘A’ 和 ‘B’
    当事件循环遇上async/await时,await 前面的代码是同步的,调用此函数时会直接执行;await 后面的代码 则会被放到 Promise 的 then() 方法里。
    Node环境中的Event Loop
    在这里插入图片描述
    执行顺序:外部输入数据–>轮询阶段(poll)–>检查阶段(check)–>关闭事件回调阶段(close callback)–>定时器检测阶段(timer)–>I/O事件回调阶段(I/O callbacks)–闲置阶段(idle, prepare)–>轮询阶段。。。

timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
idle, prepare 阶段:仅node内部使用
poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
check 阶段:执行 setImmediate() 的回调
close callbacks 阶段:执行 socket 的 close 事件回调

setTimeout(()=>{
    console.log('timer1')

    Promise.resolve().then(function() {
        console.log('promise1')
    })
})

setTimeout(()=>{
    console.log('timer2')

    Promise.resolve().then(function() {
        console.log('promise2')
    })
})
//  time1 promise1 time2 promise2 (浏览器)
// time1 time2 promise1 promise2 (Node)

在Node端时,当程序进入timers阶段,会执行timer1的回调函数,打印timer1,并将promise.then回调放入microtask队列,同样的步骤执行timer2,打印timer2。

setTimeout(function () {
   console.log(1);
});
console.log(2);
process.nextTick(() => {
   console.log(3);
});
new Promise(function (resolve, rejected) {
   console.log(4);
   resolve()
}).then(res=>{
   console.log(5);
})
setImmediate(function () {
   console.log(6)
})
console.log('end');

// 2 4 end 3 5 1 6

首先执行同步任务中的2 4 end,然后是microTask队列中的process.nextTick:3、promise.then:5,最后是macroTask队列中的setTimeout:1、setImmediate:6,由于Timer优于Check阶段,所以先1后6。

注意: 在Node 11以后:Node的事件循环和浏览器的行为统一了,都是每执行一个宏任务就执行完微任务队列。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值