Event Loop 是什么

浅谈Event Loop

从单线程说起

众所周知,js是一种单线程语言。为什么是单线程呢?我引用一句烂大街的话:假设js同时有两个线程,一个线程想要在某个dom节点上增加内容,另一个线程想要删除这个节点,这时要以哪个为准呢?当然,多线程有多线程的解决办法,加锁啊,但是这样的话,又会引入锁、状态同步等问题。

js是浏览器脚本语言,主要用途是与用户互动,操作dom,多线程会带来很复杂的同步问题。

好吧,那就单线程吧。但是单线程又带来了单线程的问题,只有一个线程啊,任务要排队执行,如果前一个任务执行时间很长(ajax请求后台数据),后面的任务就都得等着。

Event Loop就出现了,来背单线程的锅。

任务队列

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。

JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

宏任务&微任务

整理了一下常见了微任务、宏任务

  • 常见的宏任务:setTimeout、setInterval、I/O、setImmedidate
  • 常见的微任务:process.nextTick、MutationObserver、Promise.then、 catch finally、ajax请求

process.nextTick和setImmidate是只支持Node环境的。且process.nextTick是有一个插队操作的,就是说他进入微任务队列时,会插到除了process.nextTick 其他的微任务前面。

所以,我们上面提到的任务队列,是包括一个宏任务队列和一个微任务队列的。每次执行栈为空的时候,系统会优先处理微任务队列,处理完微任务队列里的所有任务,再去处理宏任务。

Event Loop

往下看之前你应该知道栈、队列、同步任务、异步任务(宏任务&微任务)、执行栈这些基本概念。

请看下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-utRj5RTq-1588226339527)(images/event-loop.png)]

1、js在执行代码时,代码首先进入执行栈,代码中可能包含一些同步任务和异步任务。同步任务都在主线程(这里的主线程就是JS引擎线程)上执行。同步任务立即执行,执行完出栈,over。
2、异步任务会再分为宏任务和微任务。微任务会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中。宏任务也会进入到Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中。
3、当主线程内的任务执行完毕,主线程为空时,会检查微任务的Event Queue,如果有任务,就全部执行,如果没有就执行下一个宏任务。

以上三步会不断重复,这就是事件循环(Event Loop)。

demo

来看一个简单的demo。

setTimeout(function() {
    console.log('1');
})

new Promise(function(resolve) {
  resolve()
  console.log('2');
}).then(function() {
  console.log('3');
})

console.log('4');

//打印顺序 2 4 3 1

上面demo的图解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hgUHzlJg-1588226339546)(images/demo1.png)]

再看一个demo

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2');
}
console.log('script start');
setTimeout(function() {
  console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
  console.log('promise1');
  resolve();
}).then(function() {
  console.log('promise2');
});
console.log('script end');

// 打印顺序是:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

看到async/await不必紧张,语法糖而已。async表示函数里有异步操作,await之前的代码该怎么执行怎么执行,await右侧表达式照常执行,后面的代码被阻塞掉,等待await的返回。返回是非promise对象时,执行后面的代码;返回promise对象时,等promise对象resolved时再执行。

所以可以理解成后面的代码放到了promise.then 里面。

  • 输出 script start
  • 之后把setTimeout里面的匿名回调函数丢进宏任务队列,简记为[‘setTimeout’]
  • 输出async1 start
  • 输出async2
  • 要输出async1 end代码被丢进微任务队列,此时的微任务队列为[‘async1 end’]
  • 输出promise1
  • promise对象状态变为resolved
  • promise.then 里的匿名函数进入微任务队列,此时的微任务队列为[‘async1 end’, ‘promise2’]
  • 输出script end
  • 执行栈空
  • 输出async1 end
  • 输出promise2
  • 微任务队列为空
  • 输出setTimeout

以上两个demo就是对Event Loop的一个练习,有哪些问题欢迎指正。

猜你喜欢

转载自blog.csdn.net/kang_k/article/details/105860367