js执行栈与事件循环简单理解

2021-08-18 12:10:16 浏览数 (1)

JavaScript 是如何异步和单线程的,这个是大多数人知道的一句话,但是理解其真正原理的并不是太多....

简单的来说,JavaScript语言是单线程的,但是他表现出的异步行为并不是语言层面的东西,而是依赖于浏览器内核的,这种异步的特性是通过浏览器API表达出来的。浏览器API内置在Web 浏览器中,它们不是 JavaScript 语言本身的一部分,而是建立在核心 JavaScript 语言之上,可以访问用户机器底层能力的API,比如,访问用户的定位信息,这个实际上背后可能是C 的一些实现,拿到定位数据,返回给到浏览器。

执行栈

执行栈简单的来说,就是执行函数的堆栈,举个例子:

代码语言:javascript复制
function main(){
  console.log('A');
  setTimeout(
    function display(){ console.log('B'); }
  ,0);
  console.log('C');
}
main();
//  Output
//  A
//  C
//  B

以上函数执行的过程就可以用下面的执行栈来表现出来:

  1. 首先main函数作为入口函数,肯定是第一个优先放到执行栈中的;
  2. console.log('A');是一个函数,虽然用得很多,但是确实是执行一个函数,想console控制台输出文本‘A’,在main函数没执行完时,他就被压入栈中,这很好理解,main函数执行完成必须依赖他的执行。由于console.log('A');是一个不依赖任何其他函数的语句,因此,他一上场就被秒杀了。
  3. 随后setTimeout( function display(){ console.log('B'); } ,0);被压入栈中,在main的上面,同理,他被执行了,但是他的执行似乎不同,留下了一个尾巴,function display(){ console.log('B'); },他被Browser Api搜集,并且丢到了 Message Queue中去了,但实际上setTimeout这个函数确实是执行完毕了,因此他退出了栈。
  4. 随后 console.log('C');被压入栈执行,不依赖任何函数,因此也被秒杀了
  5. 然后main函数走到了人生的尽头,此时执行栈空了!!!,重要事情说三遍,执行栈空了!!!执行栈空了!!!执行栈空了!!!
  6. 所以,执行栈空了,此时 Message Queue才有了释放自己收集的任务的机会,此时,function display(){ console.log('B'); }这个函数里的执行体被压入到执行栈中去执行,其实就是console.log('B')

所以 setTimeout(function, delayTime) 中的延迟参数并不代表函数执行后的精确时间延迟。它代表最短等待时间,在此之后的某个时间点将执行该函数,也可以好不怀疑的说,等执行栈空了,他才有机会出现表现自己

事件循环

所以,事件循环其实就是js代码借助与浏览器API向消息队列中丢入一些回调函数,等待执行栈放空自己的时候,把消息队列中的回调函数压入到执行栈中执行的这么一个机制。

为了证明我的结论所言不虚,我就用一个极端的例子来标明下:

代码语言:javascript复制
function main(){
  console.log('A');
  setTimeout(
    function exec(){ console.log('B'); }
  , 0);
  runWhileLoopForNSeconds(3);
  console.log('C');
}
main();
function runWhileLoopForNSeconds(sec){
  let start = Date.now(), now = start;
  while (now - start < (sec*1000)) {
    now = Date.now();
  }
}
// Output
// A
// C
// B
  1. 起初main函数被压入栈中执行,在执行过程中,发现有内容 console.log('A');
  2. 于是,main得不到退出,console.log('A');被压入执行栈中,然后立即执行了,打印了A,紧接着,
  3. setTimeout( function exec(){ console.log('B'); } , 0);被压入执行栈中,setTimeout函数也被立即执行了,但由于其特殊性,浏览器API将其包裹的回调函数交给了消息队列。因为此时执行中中mian函数还没有执行玩,所以,消息队列里面的消息只能望穿秋水的等待着....
  4. 紧接着runWhileLoopForNSeconds(3);被压入了执行栈中,是一个函数,由于js是单线程的,因此mian也好,runWhileLoopForNSeconds也好,都会在这个执行栈所在在执行上线文中孤独的执行着,不受其他人影响,所以,runWhileLoopForNSeconds默默的执行了3s,终于执行玩,然后看下main函数执行玩没,还没有,还有 console.log('C');没执行
  5. 所以,console.log('C');被压入了执行栈,然后秒执行了,此时main总算走空了,因此事件循环现在就看消息队列中有没有消息了,已看发现有,嘿,一个一个的丢出来,放到执行栈中来执行。
总结

所以,只有当执行栈中是空的时候,事件循环机制才有机会把消息队列中的任务丢出来执行,换句话说,只有执行栈中有内容在执行,事件循环就不可能给你从消息队列中取任务出来执行。如果英语比较好,可以看看这个视频,一瞬间秒懂 https://www.youtube.com/watch?v=8aGhZQkoFbQ,

附录&参考资料

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

0 人点赞