js运行的环境咱们称之为宿主环境,目前有三种运行环境,一种运行在浏览器(javaScript),一种运行在服务端(nodejs),另外一种是运行在咱们的客户端(好比Vscode客户端就是使用js写的),所以只要给js配备的相应的执行引擎,js能够运行在任何环境java
JS引擎node
GUI引擎web
事件监听线程 (DOM事件,window窗口事件等等)ajax
计时线程(SetTimeout、setInterval 计时器)api
网络线程:(ajax网络请求)浏览器
事件队列在不一样的宿主环境中有所差别,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:bash
MutationObserver 用于监听某个DOM对象的变化网络
当执行栈清空时、JS引擎首先会将微任务中的全部任务依次执行结束,若是没有微任务,执行宏任务数据结构
事件循环分为三个部分,分别由 浏览器宿主,web api 与 事件队列(也称任务队列)组成异步
执行栈
因为JavaScript 引擎是单线程,同一时间只能执行一个任务,其余任务都得按照顺序排队等待被执行,只有当前的任务执行完成以后才会往下执行下一个任务,所以这些任务被排队在一个单独的地方。这个地方被称为执行栈
同步任务、异步任务
同步任务:当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,而后从头开始执行。若是当前执行的是一个方法,那么js会向执行栈中添加这个方法的执行环境,而后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。。这个过程反复进行,直到执行栈中的代码所有执行完毕。 一个方法执行会向执行栈中加入这个方法的执行环境,在这个执行环境中还能够调用其余方法,甚至是本身,其结果不过是在执行栈中再添加一个执行环境。这个过程能够是无限进行下去的,除非发生了栈溢出,即超过了所能使用内存的最大值。
异步任务:js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其余任务。当这个异步事件返回结果后,js会将这个事件加入与当前执行栈不一样的另外一个队列,咱们称之为事件队列事件队列(事件队列是先进先出的数据结构),被放入事件队列不会马上执行其回调,而是等待当前执行栈中的全部任务都执行完毕。异步函数的执行事件,会被宿主环境控制。
事件循环(Event Loop)
主线程处于闲置状态时,主线程会去查找事件队列是否有任务。 若是有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,而后执行其中的同步代码…,如此反复,这样就造成了一个无限的循环。JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。
举个栗子
<script>
function a() {
console.log("a")
b();
}
function b() {
console.log("b");
c();
}
function c() {
console.log("c")
}
console.log("global");
a();
</script>
复制代码
运行流程:在JS代码执行以前<script>
,首先会初始化一个全局执行上下文(也称执行环境),并push到栈顶,接下来开始在全局执行环境下,执行代码,首先将三个函数定义,提取到全局上下文里去,而后继续执行,等执行到console.log('c')
以后,会调用console对象里的一个log函数,(调用任何一个函的时候,都会为这个函数建立一个函数执行上下文),而后建立log的执行上下文,并push入栈,而后就能够运行这个函数了,(始终记住js引擎永远执行的是执行栈的最顶部),运行该函数后,控制台输出一个"global",而后该函数运行结束,销毁该执行上下文,并从栈顶被推出(出栈),此时又回到了全局执行环境之下继续执行,此时又遇到了a函数的调用,调用该函数,建立一个a的函数执行上下文,并push入栈,而后开始执行a函数里面的代码,在a函数里又遇到里console对象里的log函数,又为这个log函数建立一个log的函数执行上下文,并push到栈顶,而后运行log函数,控制台输出a,以后log函数执行结束,销毁log的上下文,回到a的函数执行上下文上,继续运行,而后又发现b函数的调用,此时建立b的函数执行上下文,并push入栈,而后运行b函数,在b函数里发现console对象里的log函数的调用,而后继续为这个log函数建立log的函数执行上下文,并push入栈,而后运行该log函数并在控制台输出"a",此时该log函数执行结束,销毁其log的上下文,并从栈顶被推出(出栈pop),此时又回到了b的函数执行上下文环境下执行,继续运行,发现了c函数的调用,而后为c函数建立C的函数执行上下文,并push入栈,而后开始执行c里面的代码,执行代码过程当中,发现了console对象里的log函数被调用,又为这个log函数建立一个log的函数执行上下文,并push到栈顶,在该log环境里,运行该函数,输出"c",以后该log函数运行结束,销毁其log上下文,并从栈顶被推出(出栈pop),而后回到c的执行环境中,此时已经没有能够运行的数据了,则c运行结束,销毁c的函数执行上下文,并从栈顶被推出(出栈pop),而后回到b的执行环境之中,c的调用结束以后,b也没有能够执行的代码,此时b函数也运行结束,则销毁b的函数执行上下文,并从栈顶被推出(出栈pop),此时回到a的执行环境执行,a函数在b函数的调用结束后也没有可再执行的代码,所以a函数也执行结束,销毁a的函数执行上下文,并从栈顶被推出(出栈pop),最终执行权又回到了全局执行环境里,在全局执行环境里,发现已经没有可运行的代码了,而后也销毁全局执行上下文,并从栈顶推出(出栈pop),此时执行栈里面都为空,若是事件队列有事情,则会把执行事件队列里的代码。