浏览器中js执行机制学习笔记
同步任务
当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。如果当前执行的是一个方法,那么js会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。这个过程反复进行,直到执行栈中的代码全部执行完毕。
这个同步代码的执行过程可以是无限进行下去的,除非发生了栈溢出,即超过了所能使用内存的最大值。
异步任务
js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...如此反复
事件队列
我们先看两个实验
// 实验一 console.log('进入页面') setTimeout(() => { console.log('setTimeout1') }, 0) new Promise((resolve) => { console.log('promise') setTimeout(() => { console.log('setTimeout2') resolve() }, 0) }).then(() => { console.log('then') }) console.log('代码执行结束') /* 打印结果 进入页面 promise 代码执行结束 setTimeout1 setTimeout2 then */
// 实验二 console.log('进入页面') setTimeout(() => { console.log('setTimeout1') }, 0) new Promise((resolve) => { console.log('promise') resolve() }).then(() => { console.log('then') }) console.log('代码执行结束') /*打印结果 进入页面 promise 代码执行结束 then setTimeout1 */
上面的两个demo中,demo2里在Promise里使用了setTimeout包了一层。造成then方法中的回调函数执行顺序稍有不同。一个在setTimeout之后,一个在setTimeou之前。为什么会造成这样的结果?
因为事件分为 macro-task
、micro-task
。不同的事件会进入不同的队列。
也就是说事件队列对应的也有两个,macro queue
和 micro queue
。
- macro-task(宏任务):包括整体代码script,setTimeout,setInterval
- micro-task(微任务):Promise,process.nextTick
上文中提到当主线程闲置后,会去事件队列中查找执行回调函数,这个是有一个先后策略的,先micro queue
后macro queue
。而这里的promise中只有在执行了resolve()
后才会向micro queue
中push在then
方法里写的回调函数。这也是上边两个实验中console.log('then')
执行顺序不同的原因,下面是详细的执行顺序:
实验1: // 主线程开始执行同步代码 进入页面 //遇到setTimeout1异步挂起,接着执行同步代码 promise //遇到setTimeout2异步挂起,接着执行同步代码 代码执行结束 // 同步代码执行完毕,主线程闲置,检查micro queue为空,检查macro queue 有两个回调函数 setTimeout1 setTimeout2 //执行了resolve(),向micro queue中push回调函数 //同步代码执行完毕,主线程闲置检查micro queue,执行里边的回调函数 then
实验2: // 主线程开始执行同步代码 进入页面 //遇到setTimeout1异步挂起,接着执行同步代码 promise //执行了resolve(),向micro queue中push回调函数 代码执行结束 // 同步代码执行完毕,主线程闲置,检查micro queue发现有回调函数执行 then // 同步代码执行完毕, 主线程闲置, 检查micro queue为空,检查macro queue发现有回调函数并执行 setTimeout1
总结
不同类型的任务会进入对应的Event Queue,比如setTimeout和setInterval会进入相同的Event Queue。
事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。