JS事件循环

上周写了篇关于setTimeout的文章,其实也就牵扯到了js的运行机制。因此,这周就来谈谈javascript的运行机制吧。

那就先问个问题吧😁。javascript

为何JavaScript是单线程的?

仍是我来回答吧:

单线程意思就是说同一个时间只能作一件事。那这样的话效率不是很低?也没有啦,其实javascript的单线程特色是跟他的用途有关的。做为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操做DOM。假如不是单线程的话,在一个线程当咱们在给某个DOM节点增长内容的时候,另外一个线程正在删除这个DOM节点的内容,那还得了,那不是乱套了吗。因此javascript只能是单线程。html

虽然javascript是单线程,可是javascript中有同步和异步的概念,解决了js阻塞的问题。

同步和异步:

一.同步:

若是在一个函数返回的时候,调用者就可以获得预期结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的。
java

用代码解释一下:node

console.log('Hello');
复制代码

若是在函数返回时,就看到了预期的效果:在控制台打印了Helloajax

二.异步:

若是在函数返回的时候,调用者还不可以获得预期结果,而是须要在未来经过必定的手段获得,那么这个函数就是异步的。
promise

代码解释:浏览器

fs.readFile('test.txt', 'utf8', function(err, data) {
    console.log(data);
});
复制代码

在上面的代码中,咱们但愿经过fs.readFile函数读取文件foo.txt中的内容,并打印出来。可是在fs.readFile函数返回时,咱们指望的结果并不会发生,而是要等到文件所有读取完成以后。若是文件很大的话可能要很长时间。bash

小总结:

  1. 同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
    异步

  2. 异步方法调用更像一个消息传递,一旦开始,方法调用就会当即返回,调用者就能够继续后续的操做。而,异步方法一般会在另一个线程中,“真实”地执行着。整个过程,不会阻碍调用者的工做。函数

javascript就能够进行同步任务和异步任务。把读文件这种操做,ajax请求这些须要耗时的任务放到任务队列中,我仍是可以一步步的继续下面的任务。因此啊,javascript仍是能够很6。那么异步任务里面只是放要进行异步操做的任务吗,里面会发生啥呢?

任务队列:

上面说过了javascript里面的任务有两种,同步任务和异步任务。

同步任务是指:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

异步任务指的是,不进入主线程、而进入"任务队列"的任务,只有"任务队列"通知主线程,某个异步任务能够执行了,该任务才会进入主线程执行。

先看个小栗子吧:

console.log("a");
setTimeout(function () {
    console.log("b");
},0);
console.log("c");

//a
//c
//b
复制代码

js中代码从上往下执行,执行第一行代码的时候控制台输出a,执行到第二行代码的时候遇到了setTimeout函数,由于setTimeout函数是个异步函数,因此,浏览器会记住这个事件,添加到时间表中,以后把这个事件的回调函数入栈到任务队列中。而此时主线程程序继续往下运行,到了第五行:console.log("c"),执行这条,控制台输出c。这时候主线程空了,他会到任务队列里面去查找是否有能够执行的任务,有的话直接拿出来执行,没有的话会一直去询问,等到有能够执行的。

为了更好的说明任务队列和事件循环,先看一张图。😄

这张图片里面已经画出了js的事件循环的流程了。 流程:

  1. 全部同步任务都在主线程上执行,造成一个执行栈。
  2. 当主线程中的执行栈为空时,检查事件队列是否为空,若是为空,则继续检查;如不为空,则执行3;
  3. 取出任务队列的首部,压入执行栈;
  4. 执行任务;
  5. 检查执行栈,若是执行栈为空,则跳回第 2 步;如不为空,则继续检查;

Event Loop:

事件循环其实就是入栈出栈的循环。上面例子中说到了setTimeout,那setInterval呢,Promise呢等等等等,有不少异步的函数。可是这些异步任务有分宏任务(macro-task)和微任务(micro-task):

macro-task包括:script setTimeout, setInterval, setImmediate, I/O, UI rendering。

micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver。

每一次Event Loop触发时:

  1. 执行完主执行线程中的任务也就是执行第一个macro-task任务,例如script任务。
  2. 取出micro-task中任务执行直到清空。
  3. 取出macro-task中一个任务执行。
  4. 取出micro-task中任务执行直到清空。
  5. 重复3和4。
其实promise的then和catch才是microtask,自己的内部代码不是。

注意:

  1. 在浏览器浏览器和node中的执行不同。

  2. 任务队列里面是“先入先出”的。

那来个小栗子测试一下你是否是已经彻底理解啦:
console.log('global')

for (var i = 1;i <= 5;i ++) {
  setTimeout(function() {
    console.log(i)
  },i*1000)
  console.log(i)
}

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

setTimeout(function () {
  console.log('timeout2')
  new Promise(function (resolve) {
    console.log('timeout2_promise')
    resolve()
  }).then(function () {
    console.log('timeout2_then')
  })
}, 1000)
复制代码

控制台输出:

这个博客可以很是好的加深理解: 深刻理解 JavaScript 事件循环(一)— event loop

总结:

这里主要是讲了在浏览器端js事件循环。这篇文章能够帮助更好的理解node和浏览器环境下不一样的事件循环:浏览器和Node不一样的事件循环(Event Loop)