Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS事件循环 #26

Open
laizimo opened this issue Sep 8, 2017 · 1 comment
Open

JS事件循环 #26

laizimo opened this issue Sep 8, 2017 · 1 comment

Comments

@laizimo
Copy link
Owner

laizimo commented Sep 8, 2017

涉及到多个异步事件执行的时候,大家会不会产生思考!执行顺序,还和原来一样么?还是有什么固定的规则,让我们去判断!今天我们就来了解一下Event Loop的概念。

在聊起这个概念之前,我们先来听一段JavaScript语言的自述。

浏览器为什么选择了我

众所周知,JavaScript是一门单线程的语言。为什么浏览器会去选择它呢!原因就是——简单。在浏览器端,复杂的UI环境会限制多线程语言的开发。例如:

一个线程在操作一个DOM元素的时候,另一个线程需要去删除这个DOM元素。这种情况下,我们就需要进行状态的同步。可怕的是,浏览器往往不止去操作一个DOM元素!所以,为了避免开发中处理这种复杂的情况,单线程语言不失为一种好的解决方案。

但是,单线程也会有它的缺陷——同步阻塞。如图所示:

image

CPU在进行一个I/O操作的时候,需要去请求数据,期间需要等待数据返回之后,才能够继续执行下面的任务。这个等待期,就阻塞了其他任务的执行。因此,JavaScript在执行过程中,将任务分成了同步任务异步任务,来解决类似的情况。

同步/异步

每个线程都有一个执行栈,会根据先进后出的顺序来执行线程中的任务,所有的同步任务,都会被放到这个执行栈中,我们可以来看一段代码:

function fun1(){
  return 'hello hip-hop';
}

function fun2(){
  return fun1();
}

function fun3(){
  console.log(fun2());
}

fun3();   //'hello hip-hop'

它的执行顺序如下:

image

或者我们可以通过浏览器后台的报错来看整个执行顺序,如下:

function fun1(){
  throw new Error('hello hip-hop');
}

function fun2(){
  return fun1();
}

function fun3(){
  console.log(fun2());
}

fun3();

浏览器后台的报错提示,如图:

image

在此基础上,我们如果加入异步任务,会发生什么样的情况呢,如下:

console.log('first');
setTimeout(() => {
  console.log('second');
}, 500);
console.log('three');

我们依然通过画图的形式,来直观地感受一下执行栈的顺序,如图:

image

从图中,我们可以清晰地看到setTimeout执行完成之后,就出栈了!那么,后来的console.log('second')是如何入栈的呢?

其实,在主线程之外,还存在一个任务队列。异步任务,都会被放到任务队列中。只有当指定事件触发之后,异步任务才会被放到主线程中执行。

任务队列中,是一个事件队列。拿setTimeout举例来说:

  • 当主线程执行到setTimeout的时候,会创建一个定时器;
  • 一旦定时器的到达时间,就会将回调函数放到任务队列中;
  • 当主线程任务执行完成之后,就会去循环任务队列,执行回调函数;

事件循环

上述流程,规范为一张图如下:

这里有个循环,是一个死循环,无论哪种情况都是闭环,这个就是事件循环。事件循环不断地在检测队列是否存在已触发的任务,如果有的话,就放到主线程中执行(注:这个过程往往在主线程执行完之后进行)。

这幅图里面,我们看到了有浏览器的点击事件、ajax请求、Promise等这里。但是不同之处在于,它们的任务性质存在不同。

自从,ES6出现之后,Promise逐渐被开发者热议。这里我们来讨论一下它这方面的特殊性。

首先,我们来看一段代码:

setTimeout(() => {
  console.log(1);
}, 0);

Promise.resolve().then(() => {
  console.log(2);
}).then(() => {
  console.log(3);
});

console.log(4);      // 4   2    3    1

你会不会有所疑问,为啥不是4 1 2 3的顺序呢?

其实,这个执行顺序和任务队列有关系!任务队列中存在两种队列类型:宏任务微任务。宏任务可以有多个队列,微任务只能有一个!同时,宏任务是一个一个出队的,而微任务是一队一队出队的

在执行事件循环的过程中:

  • 主线程会先遍历一遍微任务队列,然后将队列中的函数抽离出来执行。
  • 执行完成之后,再执行一个宏任务,在循环一遍微任务队列,再执行一个宏任务
  • 直至队列都循环完毕。

了解清楚这个后,我们再回头看,心中亦如明镜。setTimeout是宏任务,Promise是微任务。具体分类如下:

  • 宏任务:setTimeout,setInterval,JavaScript(整段代码),I/O操作,UI渲染等
  • 微任务:Promise,process.nextTick(NodeJS)等

总结

本文我们回顾了:

  • JS的线程机制,同步和异步操作
  • 事件循环的过程
  • 任务队列的不同

希望对于你来说有所收获,感谢阅读。

欢迎您扫一扫上面的微信公众号,订阅我的博客!

@laizimo
Copy link
Owner Author

laizimo commented Sep 12, 2017

@laizimo laizimo changed the title 浏览器的事件循环 js的事件循环 Sep 13, 2017
@laizimo laizimo changed the title js的事件循环 JS事件循环 Apr 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant