JavaScript同步、异步以及事件轮询

558 阅读6分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文是学习前端小智的学习笔记整理,还有以及宏任务微任务的一些理解。juejin.im/post/5d8020…

🚀 JS 是一门单线程的编程语言,这就意味着一个时间里只能处理一件事,也就是说JS引擎一次只能在一个线程里处理一条语句。 这个想必大家都知道。

单线程简化了编程代码,但是也意味着在阻塞主线程的情况下执行长时间的操作,如网络请求。从API请求一些数据,根据具体的情况,服务器需要一些时间来处理请求,同时阻塞主线程,使网页长时间处于无响应的状态。由此引入了异步。

1.调用栈:

调用栈这个东西,个人理解就跟盖房子砌墙的感觉一样,最开始盖上去的砖头🧱都压在下面了,个人是这么理解last in first out的,也就常说的LIFO;下面来看下下面代码:

 const two = () => {
 		console.log('我是第二个完事的');
  }
    
 const one = () => {
    console.log('我第一个完事儿');
    two();
    console.log('我是最后一个😱');
 }  
one();
  • 当执行此代码时,将创建一个全局执行上下文(由main()表示)并将其推到调用堆栈的顶部。当遇到对one()的调用时,它会被推送到堆栈的top顶部
  • 接下来,console.log('我第一个完事儿')被推送到堆栈的顶部;当它完成时,它会从堆栈中弹出。之后,我们调用two(),因此two()函数被推到堆栈的顶部。
  • two函数中console.log('我是第二个完事的')被推送到堆栈顶部,并在完成时弹出堆栈。two() 函数也结束,因此它从堆栈中弹出。
  • console.log('我是最后一个😱');被推到堆栈的顶部,并在完成时删除。最后,one()函数完成,因此从堆栈中删除它。
  • 程序在这一点上完成了它的执行,所以全局执行上下文(main())从堆栈中弹出。

2.异步代码是如何执行的:

来看下下面的代码:

const mockReq = () => {
  setTimeout(() => {
    console.log('憨皮,我是异步中的代码code');
  }, 2000);
};

console.log('你好呀,GAMEBOY~!');

mockReq ();

console.log('the end');
  • 当上述代码在浏览器中加载时,console.log(' 你好呀,GAMEBOY~!') 被推送到堆栈中,并在完成后弹出堆栈。接下来,将遇到对 mockReq() 的调用,因此将它推到堆栈的顶部。
  • mockReq()中的 setTimeout() 函数被调用,因此它被推到堆栈的顶部。setTimeout()有两个参数:
    • 回调函数
    • 以毫秒为单位的时间
  • setTimeout() 方法在web api环境中启动一个2s的计时器。此时,setTimeout()已经完成,并从堆栈中弹出。 console.log(“the end”) 被推送到堆栈中,在完成后执行并从堆栈中删除。
  • 同时,计时器已经过期,现在定时器里面的回调函数被推送到消息队列。但是它不会立即执行,这就是事件轮询开始的地方。

3.事件轮询:

事件轮询的工作是监听调用堆栈,并确定调用堆栈是否为空。

  • 调用堆栈:

    • 调用堆栈不为空:就执行里面的内容
    • 调用堆栈是空的:它将检查消息队列,看看是否有任何挂起的回调等待执行。

    在这种情况下,消息队列包含一个回调,此时调用堆栈为空。因此,事件轮询将回调推到堆栈的顶部

    然后是 console.log(“憨皮,我是异步中的代码code”) 被推送到堆栈顶部,执行并从堆栈中弹出。此时,回调已经完成,因此从堆栈中删除它,程序最终完成。

4.宏任务和微任务:

浏览器中的事件轮询eventLoop,分为

  • 同步执行栈:优先执行同步任务
  • 异步消息队列:当同步任务执行完之后会从异步消息队列中取异步任务,拿到同步执行栈中进行执行

🌵异步消息队列的中的任务可分为下面两类:

宏任务和微任务:

  • 宏任务:优先级低,先定义的先执行。包括:ajax,setTimeout,setInterval,事件绑定,postMessage,MessageChannel(用于消息通讯)
  • 微任务,优先级高,并且可以插队,不是先定义先执行。包括:promise 中的 then,observer,MutationObserver,setImmediate

还是通过例子来理解消化吧,只可意会,尽量言传。

🚀GAME1:

setTimeout(function() {
  console.log(4);
}, 0);

new Promise(function(reslove) {
  console.log(1);
  reslove();
}).then(function(data) {
  console.log(3);
});

console.log(2);

//会输出:1,2,3,4。

🏖GAME1-PROGRESS:

  • 先将 setTimeout 加入到异步消息队列的宏任务池

  • 然后执行 promise 中的console.log(1)

  • 再将 promise.then 加到异步消息队列微任务池

  • 再执行同步任务console.log(2),当同步任务都执行完之后,先取微任务队列中的任务,执行console.log(3)

  • 微任务执行完之后取宏任务,执行console.log(4)

    所以顺序就是:1,2,3,4。

    注意📢:

🌵promise 中是同步任务,promise 的 .then 中是异步任务~!!!

🚀GAME2:

setTimeout(function() {
  console.log(4);
}, 0);

new Promise(function(reslove) {
  console.log(1);
  setTimeout(function() {
    reslove("我勒个擦MMP"); //这里需要注意,这里如果是reslove就不会执行的,如果是console.log就会执行
  	console.log("你的名字叫十二");
  }, 0);
  reslove('first');
}).then(function(data) {
  console.log(data);
});

console.log(2);
//输出结果:
//1,2,first,4,你的名字叫十二

🏖GAME2-PROGRESS:

这个时候就会输出:1,2,first,4,你的名字叫十二,没有输出"我勒个擦MMP"

有些人可能会想,应该输出 1,2,first,4,你的名字叫十二,我勒个擦MMP,这个时候就要知道,当使用 reslove 之后,promise 的状态就从 pending 变成了 resolve

promise 的状态更改之后就不能再更改了,所以 reslove('我勒个擦MMP') 就不会执行了。

🚀GAME3:

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

new Promise(function(reslove) {
  console.log(2);
  reslove('p1');
  new Promise(function(reslove) {
    console.log(3);
    setTimeout(function() {
      reslove('setTimeout2');
      console.log(4);
    }, 0);
    reslove('p2');
  }).then(function(data) {
    console.log(data);
  });
  setTimeout(function() {
    reslove('setTimeout1');
    console.log(5);
  }, 0);
}).then(function(data) {
  console.log(data);
});

console.log(6);

//打印结果:
// 2,3,6,p2,p1,1,4,5

🏖GAME3-PROGRESS:

  • 先执行同步的任务,new Promise 中的都是同步任务,所以先输出 2,3,6

  • 然后再执行微任务的,这里面会先执行p2里面的逻辑,然后再走外层逻辑。resolve()是用来表示promise的状态为fullfilled,相当于只是定义了一个有状态的Promise,但是并没有调用它;promise调用then的前提是promise的状态为fullfilled;只有promise调用then的时候,then里面的函数才会被推入微任务中

  • 然后执行 p1,当微任务都执行完成之后,执行宏任务,宏任务依次输出 1,4,5,promise 的状态不可以变更,所以 setTimeout1 setTimeout2 不会输出。

🌵特别声明

在node环境下,process.nextTick的优先级高于Promise,也就是说:在宏任务结束后会先执行微任务队列中的nextTickQueue,然后才会执行微任务中的Promise。