深入浅出的JS执行机制(图文教程)

前序

作为一个有理想有抱负的前端攻城狮,想要走向人生巅峰,我们必须将我们使用的功法练到天人合一的地步。我在们日常工作中,使用最多的语言就是JavaScript了,为了写出完美的、能装逼的代码,我们必须对JavaScript有一个非常透彻的理解,也只有这样我们才能随心所欲的去编写的代码。好了,废话不多说,接下来我们就来一起来了解一下,在JS中代码的执行机制到底是怎样的呢?

执行机制中的关键词

1.call-stack 调用堆栈

调用堆栈简单来说就是当前文件执行上下文中的表达式以及被调用的函数所构成的(未被调用的函数不存在调用堆栈中)

英文好的同学可以去WIKI百科查看详细讲解:
https://www.en.wikipedia.org/wiki/Call_stack#FRAME-POINTER

2.macro-task 宏任务

宏任务是JS中的异步执行任务,在执行call-stack时,JS 引擎会将所有宏任务放入宏任务队列中;下面是JS中的宏任务:

  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame

3.micro-task 微任务

微任务也是JS中的异步执行任务,在执行call-stack时,JS 引擎会将所有微任务放入微任务队列中;下面是JS中的微任务:

  • process.nextTick
  • MutationObserver
  • Promise.then
  • Promise.catch
  • Promise.finnaly

下图是JS的执行机制简图

通过上图,我们对JS的执行机制应该有了粗略的了解,下面我们通过代码的方式来梳理一下,JS的具体执行流程。

// 1.控制台第一步打印下面的console

console.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.最后执行下面的console

console.log('f');

上面代码的标注顺序就是代码在JS中的执行顺序,可能有的朋友会觉得有一点抽象;没关系,下面我们通过对边代码和执行队列图来看看上面代码的JS执行过程

简要概况一下JS的执行过程

在执行当前的 JS 文件时,浏览器会先把全局执行上下文中的表达式、和被调用的函数执行完成,接着再执行放入 微任务队列中的函数,最后执行放入宏任务队列的函数。

代码执行步骤详细描述

  1. 执行这一步打印 Global context;
  2. 执行 fn 方法,首先打印 ‘fn start’
  3. 接着把setTiemout 里的匿名函数放入到宏任务队列中,
  4. 接着执行Promise 并把resolve代表的函数放入微任务队列中,最后打印 ‘fn end’
  5. 接着执行全局中的setTimeout 并把里面的匿名函数放入宏任务队列中
  6. 接着执行全局中的 Promise 并把resolve 代表的函数放入微任务队列中
  7. 接着打印 ‘f’,这个时候 call-stack (调用堆栈)中的代码已经执行完毕,就会去执行第一个放入微任务队列中的函数,及在第4步中的放入的微任务,打印 ‘d’,接着执行第二个放入微任务队列中的函数,及在第6步中放入到微任务队列中的函数,打印 ‘h’
  8. 这个时候微任务队列中的任务已经全部执行完成,接着就会执行宏任务队列中的函数,即fn函数里的setTiemout中的匿名函数,打印 'c’,并把 Promise中的的a所代表的的函数放入微任务队列中。这是微任务队列中就有函数了,这是应该去执行微任务队列中的函数,所以这时就会打印 ‘hello world’,
  9. 这时候微任务队列也为空了,就会去执行宏任务队列中的函数,这时候全局的setTiemout的匿名函数就执行了,打印 ‘macro task 2’,并把函数中的 Promise 中的 a 所代表的函数放入微任务队列中,这是微任务队列也就有了可执行函数,所以现在就会执行微任务队列中的函数,打印 'hello girl’

因为讲的非常详细,所以可能显得啰嗦了,希望大家别介意。由于本人水平有限,如有不当之处还望斧正,以免误人子弟。

原文地址:https://www.cnblogs.com/abcode/p/11685372.html