一、前言:
js中浏览器的事件循环机制,对于很多初学者来说是难以理解的一个课题,本文会尽可能的用更多的案例和图解,更直白的语言,由简单到复杂的过程来分析,以初学者的状态来认识它。希望对一些初学者带来一些帮助。如果有什么不对的地方,还请指教,谢谢!
二、 同步程序和异步程序
要更好的理解浏览器事件循环机制,必须要对同步程序和异步程序有一定的认知。
-
同步程序
先来看下面一个js的同步代码
console.log(1);
console.log(2);
console.log(3);
执行结果:
image.png
结果说明:程序从上到下,一行一行的执行,执行完一行,再到下一行,所以结果分别输出:1,2,3,这种执行方式可以称之为:同步执行
举个生活中的栗子:
我们去银行柜台办理业务,谁先来的就先办理,如果前面的业务还没办理完毕,后面来的就要排队等候办理。办理完一个,才到下一下。前面一个哪怕要输1个小时,两个小时,后面排队的也得耐着性子等待。
【注:同步执行不能理解为同时执行,而是理解为在同一序列上运行。如市区中的单行道,所有车辆必须在同一个方向,同一条路线上行驶,不能超车,通过一辆再到下一辆】
稍微专业一点的理解:
javaScript是单线程的语言。浏览器js引擎(又称解析器)在解析js脚本的时候,会生成一个执行栈,执行栈中有一个主程序(这个主程序就是这条单线程),js中的所有代码,都要在这个主程序上面排队等候执行,执行完前面一个,再执行下面一个,直到最后一个程序执行执行完毕。如果主程序在执行的过程中,某个程序执行需要花费的时间比较长,后面的程序不会立即执行,而是等待这个程序执行完毕,再依次执行。
图例说明:
同步程序图示.png
文字描述:
程序中所有的同步程序,称之为执行栈中的同步任务,每一段同步程序,都可以称之为同步任务,也可以理解为要处理的事务(银行柜台办理业务就是一种事务,办理了一个人的业务,就是处理了一个事务)。
第一步:主程序执行了同步任务1;
第二步:主程序执行了同步任务2;同时,因为任务1结束,而被浏览器销毁。
第三步:主程序执行了同步任务3;同时,因为任务2结束,而被浏览器销毁。
第四步:浏览器销毁任务3,主程序找不到其他任务,执行栈为空,程序结束。
-
异步程序:
异步程序要比同步稍微难理解一点,不用担心,往下看,你就懂了!!
js的异步代码栗子:
console.log(1);
setTimeout(function callBack() {
console.log(2);
});
console.log(3);
执行结果
image.png
结果说明:可以看到,这个程序,将console.log(2)放在了一个setTimeout()方法的回调函数中执行,程序执行结果与上面的同步程序就不一样,结果是 1 3 2,这又是为什么呢?
原因:主程序只执行同步程序(同步任务)。而setTimeout中的回调函数是一个异步的程序,这个异步程序不会被立即执行,而是要等待同步程序全部执行完毕后,再来执行。所以结果是1 3 2。
【原因是描述了,具体原理又是什么呢?下面用图例来说明】
来一波图例:
任务队列示意图.png
文字描述:
第一步:主程序执行了同步任务1(log(1));
第二步:主程序执行了同步任务2(setTimeout()方法);同时,因为任务1结束,而被浏览器销毁。
第三步:setTimeout方法中的回调函数callBack是一个异步程序,主程序不会立即执行,而是将这个callBack回调推送到另外一个区域,这个区域叫异步任务队列(任务队列稍后详解),任务队列用于缓存所有对应的异步回调程序,这里的程序并不是立即执行,而是在任务队列中排队就绪,等待主程序中所有的同步任务执行完毕后再执行。
第四步:浏览器销毁同步任务2,主程序执行同步任务3(log(3)).
第五步:浏览器销毁同步任务3,此时执行栈同步任务为空,主程序闲置,等待任务队列中的任务就绪任务通知。
第六步:callBack异步任务就绪后向主程序发出通知,通知已经就绪,可以执行了。
第七步:主程序接收到任务队列某个异步任务发出的通知,调取该任务到执行
栈,主程序开启工作模式,将任务以同步任务方式执行(log(2))。
第八步:浏览器销毁callBack任务,任务队列为空,执行栈为空,主程序无事可做,程序结束
最终结果:1 3 2
【这里出现了一个重要的名词:“任务队列”,这个东西是理解浏览器事件循环机制的核心,我们把任务队列的运行原理弄明白后,就可以理解什么是浏览器事件循环机制】
异步程序运行原理的简单总结:
异步程序不会被主程序直接执行,而是被推送到任务队列中等候执行,异步队列中的异步程序也叫异步任务,当主程序中的所有同步任务执行完毕后,主程序才会到异步队列中调取异步任务到执行栈中执行。
三、任务队列
上面分析了一波同步程序和异步程序,知道了异步程序执行的基本原理。本质上就在于这个任务队列,它的执行过程是非常复杂的,很难用文字的方式描述清楚。在上一个图例中,分析了八步执行流程,这个过程的文字描述并不完整。接下来要做的事情就是对这个任务队列进行深入的分析。
- 什么是任务队列
任务队列,又叫事件队列。事件队列可以有多个不同的队列,不同的队列存放的是不同类型的异步程序。同一类型的异步程序可能有多个,多个同类型的异步程序需要在同一个队列中排队等候执行(队列有一套严格的排队机制,后面会详细说明排队原则)。这些队列中排好队的异步程序通常是以函数的形式存在,又叫异步任务。当异步程序已经成功返回了数据或者等候的时间已经到达,会立即进入到对应的队列中排队等候执行,该程序就成为了这个队列中排队等待执行的任务。而此时,队列会自动分配一个对应事件给这个任务,事件发出一个通知(消息),并传递给主程序,主程序中的同步任务一旦执行完毕,会接收到这个通知,于是主程序就会来队列中调用这个任务,将任务带回执行栈执行。因此,任务队列又称之为事件队列。
不同类型的异步程序: js中,异步程序有很多种,如:DOM的事件操作(事件处理程序),ajax回调,定时器回调。
异步程序对应的队列事件:(事件用于向主程序发送就绪通知)
1.DOM事件处理程序事件:鼠标事件、键盘事件等;
2.ajax回调程序:ajax相关事件;
3.定时器回调:定时器相关事件;
【以上事件队列描述,从文字上看起来比较抽象和难以理解,下面将会用一系列的代码与图例来说明】
- 不同类型的异步程序及对应的事件队列
上面任务队列的介绍中说明了一个问题:异步程序有多种类型,不同类型的异步程序要进入的事件队列也会不同。
1.DOM事件的处理函数,进入的队列是DOM事件任务队列,任务队列同时会给任务分配DOM相关事件;
2.ajax请求异步回调,进入的是ajax任务队列,任务队列同时会给任务分配ajax相关事件;
3.定时器异步回调,进入的是定时器任务队列,任务队列同时会给任务分配定时器相关事件;
下面用代码案例来说明异步程序,任务队列以及任务事件三者之间的关系。
console.log(1);
setTimeout(function callBack1(){
console.log(2);
});
console.log(3);
document.onclick = function dClick(){
console.log(4);
}
console.log(5);
setTimeout(function callBack2(){
console.log(6);
});
console.log(7);
$.getJSON("url.json",function jsonBack(data){
console.log(data);
})
图例:
不同类型的异步任务放入不同的任务队列中.png
文字描述:
1.callBack1与callBack2是定时器类型的异步任务,因此他们都放入定时器的任务队列
2.dClick是DOM中的事件,所以放入DOM事件对应的队列中
3.jsonBack是ajax请求的异步任务,放入ajax对应的任务队列中
4.这些队列中的任务都是以执行函数的方式存放。
- 任务队列的排队原则
console.log(1);
setTimeout(function callBack1(){
console.log(2);
},300);
console.log(3);
setTimeout(function callBack1(){
console.log(4);
},100);
console.log(5);
图例:
执行结果:1 3 5 2 4
执行结果1.png
执行原理图例:
异步任务排队原则图示.png
执行原理描述:
第一步:主程序执行log(1)同步任务;
第二步:主程序执行setTimeout()同步任务,开始计算时间(这里有300毫秒阻塞设置);
第三步:主程序执行log(3)同步任务;
第四步:主程序执行setTimeout()同步任务,开始计算时间(这里有100毫秒阻塞设置);
第五步:主程序执行log(5)同步任务;
第六步:所有同步任务执行完毕,主程序闲置,等待任务队列通知。
第七步:100毫秒时间到达,将callBack2异步回调推入任务队列,排队等候执行,同时,任务队列给callBack2添加定时器事件,并向主程序发出通知:callBack2准备就绪,可以执行了。
第八步:主程序接收到任务队列事件发出的通知,调取该事件的异步任务执行,浏览器也会从队列中将这个任务销毁。
第九步:300毫秒时间到达,将callBack1异步回调推入任务队列,排队等候执行,同时,任务队列给callBack1添加定时器事件,并向主程序发出通知:callBack1准备就绪,可以执行了。
第十步:如果callBack2,在主程序中并没有执行完毕,则继续等候。当callBack2执行完毕,浏览器会销毁callBack2在执行中的任务,主程序接收到callBack1的事件通知,从队列中调取该任务执行,浏览器也会从队列中将这个任务销毁。
事件循环机制
通过以上异步程序执行原理和任务队列排队原则,我们会发现:异步程序进入任务队列中排队等候,任务事件发出通知,主程序接收通知,并调用任务执行及销毁的这个过程,是随着异步任务不断的排队,而往复循环操作的一个过程。所以整个的这种运行机制又称为事件循环机制。