深入浅出的JS执行机制

119 阅读6分钟
原文链接: mp.weixin.qq.com

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

        在这里我们需要先理解一下本文所涉及几个的关键词:

  1. call-stack 调用堆栈

  2. macro-task 宏任务

  3. 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 文件时,浏览器会先把全局执行上下文中的表达式、和被调用的函数执行完成,接着再执行放入 微任务队列中的函数,最后执行放入宏任务队列的函数。

 步骤详解:

  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’

至此上面的代码全部执行完毕了。因为讲的非常详细,所以可能显得有点啰嗦了,希望大家别介意。由于本人水平有限,如有不当之处还望大家不吝赐教,以免误人子弟。