JavaScript事件循环机制

javaScript是单线程的语言:

  众所周知,javaScript是一门单线程语言;何为单线程?我的理解是:同一时间只能做同一件事;单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。

白话解释:

  假如去某银行办理业务,某银行的单次业务接待总量为100个客户,但此时在该银行等待办理业务的人数为150人,所以有50人是需要等待的,等待时间是未知;银行采取叫号方式进行排队办理业务,前100个人是可以完成业务办理的,此时这100个人形成了一个排队的队列,在计算机中我们称之为线程;只有线程队列中完成了任务或者空闲,剩下的50人才能继续办理业务;

javaScript为什么会是单线程的语言?

 在《javaScript高级程序设计》一书中有一个很好的解释:如果JS是多线程语言,那么假如当多个线程同时操作同一个DOM的时候,浏览器该如何渲染?浏览器该听哪个线程的指令?渲染结果是否会超出预期?基于这个特性,JS必须只能是单线程语言;

 

进程与线程的区别:

  进程:是cpu资源分配的最小单位;(能拥有独立资源和独立运行的最小单位)

  线程:是cpu资源调度的最小单位;(线程是建立在进程基础上一次程序最小的执行单位,一个进程中可以有多个线程)

  白话解释:假设进程是一个工厂,它有它的独立资源并且不受外部其他工厂影响,工厂之间互相是独立存在;而线程是这个工厂里面的工人,工人之间可以互相分工完成工作任务,并且工人数量可以不等,可以为1个也可以为多个,且工人之间的空间是共享的,能互相访问到的;
 
    我们作为前端,经常打交道的浏览器就是一个典型的多进程的程序;浏览器没打开一个tab页面就是一个单独的进程,我们浏览器可以打开多个tab页面,所以就证实了浏览器是多进程的,每个进程之间互相独立互不影响;其中浏览器中的浏览器渲染进程(又称浏览器内核)是属于多进程中的一种,主要负责页面渲染、脚本执行、事件处理等;其包含的线程有:GUI渲染线程(渲染页面、解析HTML、CSS构成DOM树)、JS引擎线程、事件触发线程、定时器触发线程、http请求线程等主要线程;
 
javaScript运行机制:
    上面我们说到JS引擎线程,是的,JS引擎线程是单线程运行的;主要职责就是负责执行js脚本;
  js中把任务分为同步任务和异步任务:
  同步任务:
    即在主进程立即执行且前一个任务没有执行完成,后面的任务不能往下执行的任务;
  异步任务:
        不进入主进程而进入任务队列的任务,只有任务队列通知主进程某个任务可以执行了,异步任务才会进入主进程开始执行;
     异步任务我们又分为宏任务(macro-task)和微任务(micro-task):     
     宏任务(macro-task)包括:整体代码script、setTimeout、setInterval
     微任务(micro-task)包括:Promise( 注意:new Promise() 是同步任务,resolve、.then、.catch等方法才是异步任务)、process.nextTick
    tips:同一轮事件循环中,微任务永远比宏任务先执行;
  所以js的事件循环如下图所示:

 

   解析图文: 

    同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table(任务队列)并注册函数。

    当指定的事情完成时,Event Table(任务队列)会将这个函数移入Event Queue(事件队列)。

    主线程内的任务执行完毕为空,会去Event Queue(事件队列)读取对应的函数,进入主线程执行。

    上述过程会不断重复循环,也就是常说的Event Loop(事件循环)。

   举个例子:

console.log('script start'); 
setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end');

  输出结果应为:

    script start
    script end
    promise1
    promise2
    setTimeout

  执行过程解析:

  首先循环浏览器会执行第一步的同步任务,当执行到setTimeout的时候发现是异步的宏任务然后放入到任务队列中,接着往下执行Promise.then,发现Promise.then也是异步任务且是微任务,继续放入到队列中接着往下走执行”sciprt end ” ;执行完”script end “之后发现主进程的同步任务都执行完了,然后开始执行异步任务,异步任务任务又分为宏任务和微任务,我们前面说过微任务会优先于宏任务执行;所以我们继续执行” promise1 “,然后往下走发现还有个.then的微任务没有执行,接着往下执行” promise2 “,至此所有的微任务执行完毕我们再去执行宏任务” setTimeout “,所以最终的执行结果应该为:script start > script end > promise1 > promise2 > setTimeout

 

  什么?学会了?上面demo太简单?那我们再看一下复杂一丢丢的例子来检验一下我们的学习成果

console.log('1');   
 setTimeout(() => {
    console.log('2')  
 }, 1000);
 new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('3');  
    }, 0);
    console.log('4');  
    resolve();
    console.log('5');  
 }).then(() => {
    console.log('6');  
 });
 console.log('7');  

  这个例子是在上面例子的基础上加以改造进行了嵌套

     执行过程解析:
  首先浏览器会执行” 1 “这个同步任务,接着往下发现是异步宏任务,放入到事件队列中接着往下走,Promise里面还是有异步宏任务,继续往下发现两个同步任务,同步任务按顺序执行所以先输出” 4 “在输出” 5 “,接着往下.then是异步微任务,放入任务队列中;然后发现同步任务执行” 7 “,本轮同步任务执行完毕以后会去执行异步队列中的微任务,所以我们接着会去执行.then方法输出” 6 “,微任务执行完成以后浏览器再执行本轮循环中的宏任务,所有浏览器再执行Promise里面的setTimeout输出” 3 “,最后我们这一轮事件循环执行完了,然后与Promise同层的” 2 “还没有执行,还在任务队列中,最后浏览器再执行且输出” 2 “;注意,事件循环是分轮次的,只有本层的任务执行完以后再会往上去执行;所以我们Promise里面的setTimeout要先于外层的setTimeout执行,这个就是因为层级的原因;所以最后的执行结果应该是:” 1 ” > ” 4 ” > ” 5 ” > ” 7 ” > ” 6 ” > ” 3 ” > ” 2 “
 
  总结:浏览器在执行代码的时候碰到同步任务会一步一步往下执行,碰到异步任务会放入到任务队列中等同步任务执行完以后再放入到主进程中执行;所以每一轮事件循环的执行顺序应该都是同步优先异步执行,异步又分为宏任务和微任务,当执行异步任务的时候微任务优先于宏任务,本轮的循环执行结束后才会去上一层进行往下执行;