作为一个有理想有抱负的前端攻城狮,想要走向人生巅峰,我们必须将我们的开发语言练到出神入化的地步,我们在日常工作中,接触最多的语言就是JavaScript了,为了写出最完美的、最装逼的、最炫酷的代码,我们必须对JavaScript有一个非常透彻的理解,也只有这样我们才能随心所欲的去编写自己的代码。好了,废话不多说,接下来我们就来看看JS中的执行机制到底是怎样的呢?
在这里我们需要先理解一下本文所涉及几个的关键词:
-
call-stack 调用堆栈
-
macro-task 宏任务
-
micro-task 微任务
调用堆栈:简单来说就是当前文件执行上下文中的表达式以及被调用的函数 所构成的(未被调用的函数不存在调用堆栈中)
英文好的同学可以去WIKI百科查看详细讲解:
https://www.en.wikipedia.org/wiki/Call_stack#FRAME-POINTER
宏任务和微任务都是JS中的异步执行任务,在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中
宏任务:包括(setTimeout、setInterval、setImmediate、requestAnimationFrame)
微任务:包括(process.nextTick、MutationObserver、Promise.then catch finally)
下图是JS执行流程,引自掘金

至此,我们对JS的执行机制有了一定的了解,下面我们通过代码的方式来理解一下,JS的具体执行流程。
首先,我们需要编写一段代码来帮助我们理解JS的执行机制,JS如下:
// 1.控制台第一步打印下面的consoleconsole.log("Global context");function fn(){ console.log("fn start"); // 3.当执行这个setTiemout时,这里的函数将被放入 // 到全局的macro-task(宏任务)队列中
setTimeout(function(){ // 当 micro-task (微任务)队列中的函数执行完毕后就会执行第 // 一个被添加到 macro-task 队列中的函数 // 11. 也就是当前的匿名函数 console.log("c");
},0)
// 4.JS执行到这里时会把resolve代表的函数放入到 // micro-task (微任务)队列中 new Promise(function(resolve){ // 当call-stack (调用堆栈)执行完毕后就会执行第一个被添加 // 到micro-task 队列中的函数 // 9. 也就是执行resolve代表的函数
resolve('d');
}).then(function(s){ console.log(s);
}) // 5.接着执行下面这行代码 console.log("fn end");
} //2.接着执行fn函数fn();// 6.接着执行setTimeout函数// 当执行这个setTiemout时,这里的函数将被放入// 到macro-task(宏任务)队列中setTimeout(function(){ // 12.这里是第二个被添加到 macro-task 队列中的 // 的函数,所以当第一个 macro-task 任务执行完后就会执行这个 // 这个函数,到此为止,所有的 call-stack (调用堆栈)、micro-task // (微任务)队列、 macro-task (宏任务)队列里的函数全部执行完 // 了,整个 JS 文件里的代码也执行完毕 console.log('macro task 2');},0);// 7.接着执行Promise函数// JS执行到这里时会把resolve代表的函数放入到 // micro-task (微任务)队列中new Promise(function(resolve){
resolve();
}).then(function(){ // 10.这里是第二个被添加到 macro-task 队列中的 // 的函数,所以当第一个 macro-task 任务执行完后就会执行这个 // 这个函数,这个又是最后一个 micro-task 任务,当它也执行完 // 成的时候,JS 就会去执行在 macro-task 任务队列中的函数 console.log('h');
}) // 8.最后执行下面的consoleconsole.log('f');
上面的代码只是一个粗略的JS执行过程,下面我们来仔细的说一下,整个JS文件的执行逻辑。
上面代码的图文执行流程

概述:在执行当前的 JS 文件时,浏览器会先把全局执行上下文中的表达式、和被调用的函数执行完成,接着再执行放入 微任务队列中的函数,最后执行放入宏任务队列的函数。
步骤详解:
-
执行这一步打印 Global context;
-
执行 fn 方法,首先打印 ‘fn start’
-
接着把setTiemout 里的匿名函数放入到宏任务队列中,
-
接着执行Promise 并把resolve代表的函数放入微任务队列中,最后打印 ‘fn end’
-
接着执行全局中的setTimeout 并把里面的匿名函数放入宏任务队列中
-
接着执行全局中的 Promise 并把resolve 代表的函数放入微任务队列中
-
接着打印 ‘f’,这个时候 call-stack (调用堆栈)中的代码已经执行完毕,就会去执行第一个放入微任务队列中的函数,及在第4步中的放入的微任务,打印 ‘d’,接着执行第二个放入微任务队列中的函数,及在第6步中放入到微任务队列中的函数,打印 ‘h’
-
这个时候微任务队列中的任务已经全部执行完成,接着就会执行宏任务队列中的函数,即fn函数里的setTiemout中的匿名函数,打印 'c’,并把 Promise中的的a所代表的的函数放入微任务队列中。这是微任务队列中就有函数了,这是应该去执行微任务队列中的函数,所以这时就会打印 ‘hello world’,
-
这时候微任务队列也为空了,就会去执行宏任务队列中的函数,这时候全局的setTiemout的匿名函数就执行了,打印 ‘macro task 2’,并把函数中的 Promise 中的 a 所代表的函数放入微任务队列中,这是微任务队列也就有了可执行函数,所以现在就会执行微任务队列中的函数,打印 'hello girl’
至此上面的代码全部执行完毕了。因为讲的非常详细,所以可能显得有点啰嗦了,希望大家别介意。由于本人水平有限,如有不当之处还望大家不吝赐教,以免误人子弟。