【深入】【01】浏览器的JavaScript执行机制

一、浏览器的JS执行机制

1、加载代码

浏览器加载到 JavaScript 代码时,会先用编译器解析一遍代码,处理好变量、作用域…之后,再交由JavaScript引擎来执行,然后会生成一个全局执行环境(只有一个),然后将全局环境,压入到执行栈。此时,执行栈只有一个栈帧,就是全局环境,然后开始执行全局环境的代码。
【深入】【01】浏览器的JavaScript执行机制_第1张图片

2、代码执行

线程按照执行栈中的栈帧,来按顺序执行。只执行栈顶的环境。执行一个环境中的代码时,同步代码,按加载进来的顺序执行,异步代码,扔进任务队列中执行(事件循环后面说):

// 假设存在代码
var a = 10;
function f1(){
    var b = 20;
}
f1();

/*
	执行的顺序:
	1. 加载代码,创建全局执行环境,然后压入到执行栈
	2. 执行代码:var a = 10;
	3. 执行代码: f1(),(注意,f1声明的代码,不会执行,只有f1调用时,才会执行)。执行f1,会再次产生一个函数的执行上下文
*/

3、同步代码中的函数调用

在同步代码执行时,遇到函数的调用,会为这个函数,生成一个自己的执行上下文,然后同样压入到执行栈。这样,执行栈的栈顶上,第一个环境,就是被压入的那个函数的执行上下文了。所以,开始执行函数的代码。函数代码执行完成后,在执行栈中,弹出函数的执行上下文,将函数的上下文销毁。执行栈栈顶的环境,又变为全局的环境,继续执行函数调用后的代码:
【深入】【01】浏览器的JavaScript执行机制_第2张图片

4、异步函数的调用

在JavaScript中,异步代码,一般是用来处理耗时的操作,比如发起网络请求、读取本地文件等。而为了保证异步代码,不阻塞主程序,采用了事件循环的机制(event loop)。浏览器执行异步操作,是使用了单独的线程,例如处理事件的事件触发线程,处理定时器的定时触发器的线程,处理请求的异步HTTP请求线程。

在JavaScript主程序中,异步操作,包括两个要素:触发异步操作的注册函数,和回调函数。JavaScript主线程执行到了异步操作的注册函数,会通知浏览器的其他异步处理线程,来执行异步操作,然后JavaScript主线程,就可以继续执行后面的代码,如果执行中,还遇到了异步任务,按照同样的方法处理。

当浏览器的异步操作线程,操作完成后,会将一条消息,放进一个消息队列。这条消息的内容,包括着异步操作的相关信息,可以简单的理解为存储了异步操作的回调函数。这个消息队列,是一个异步任务的队列。JavaScript主程序执行完后,会去异步任务队列中,读取已经返回来的异步任务,然后执行对应的回调函数。
【深入】【01】浏览器的JavaScript执行机制_第3张图片

5、事件循环

JavaScript主程序,调度异步任务队列的过程,叫做事件循环。也就是上一步中,主线程读取任务队列的过程。

当同步的代码全部执行完毕,主线程就会空闲,这时候,主线程就会去读取任务队列,查看是否有已经执行完毕的异步操作。在这个过程中,根据异步任务的不同,会存在两个任务队列:宏任务队列,和微任务队列。微任务队列中的异步任务优先级,要高于宏任务队列。所以,在JavaScript主线程开始读取回调任务时,会先读取 微任务队列,如果微任务队列有已完成的异步任务,则先执行,并且将微任务队列内所有的异步任务都执行完。当微任务队列为空,或者清空(全部执行完)后,才会去宏任务队列中,查看是否有需要执行的回调函数。如果有,则取出队列中,最前面的一个任务,用来执行,执行完毕后,结束当前循环,继续下一轮循环;如果没有,则直接结束当前循环,进行下一轮循环。
【深入】【01】浏览器的JavaScript执行机制_第4张图片

6、其余

待讨论的还有:

  • 匿名函数
  • dom事件队列遍历

你可能感兴趣的:(前端)