JavaScript浏览器事件循环机制

68 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

一、进程和线程

进程

进程是 CPU资源分配的最小单位

对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程。就像一个工厂。

线程

线程(thread)是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。

有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。就像工厂里边的工人,

二、Js为什么事单线程

js诞生之初主要是运行在浏览器中的。主要是进行DOM的操作和页面交互。当两个 JavaScript脚本同时修改页面的同一个 DOM节点时,浏览器该执行哪个呢?所以设计支出就做成了单线程。

三、函数调用栈与任务队列

函数调用栈

执行上下文之后,根据规则储存起来的队列。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。

栈:先进后出。把它想象成一个羽毛球盒子。

任务队列

可以理解成大家排队做核酸,先进先出。

四、宏任务和微任务

宏任务

script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering

微任务

process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)

在微任务中 process.nextTick 优先级高于Promise

在事件循环中,先进入全局环境。区分宏任务和微任务。第二次执行时,先执行所有的微任务,微任务执行完之后,在执行所有的宏任务。

五、案例解说

 setTimeout(function() {
     console.log('timeout1');
 })
 ​
 new Promise(function(resolve) {
     console.log('promise1');
     for(var i = 0; i < 1000; i++) {
         i == 99 && resolve();
     }
     console.log('promise2');
 }).then(function() {
     console.log('then1');
 })
 ​
 console.log('global1');
  1. 从全局环境进入,发现setTimeout,是一个宏任务,把它放到宏任务的列表中:timeout1
  2. 继续往下走,发现了Promise.Promise构造函数会立即执行,所以在global中,输出promise1,promise2。
  3. 在执行中调用了resolve(),回调函数then1,进入微任务中:then1
  4. 发现console,执行输出global1。
  5. 这样第一次的循环完成,进入所有的微任务中,发现then1,然后输出then1
  6. 检查微任务是否执行完成。OK已完成,执行宏任务,发现timeout1,然后输出timeout1
  7. 检查所有的宏任务是否执行完成,OK已完成,调用栈空了,任务结束。

结果

 promise1,promise2,global1,then1和timeout1

总结

  1. 从全局任务 script开始,任务依次进入栈中,被主线程执行,执行完后出栈。遇到不同的任务,我们进行区分宏任务和微任务
  2. 当调用栈为空,同步任务任务执行完毕时,会先执行微任务队列里的任务,微任务队列里的任务全部执行完毕后,会读取宏任务队列中拍最前的任务
  3. 执行宏任务的过程中,遇到微任务,依次加入微任务队列,当调用栈为空后,再次读取微任务队列里的任务,依次类推