Javascript 运行机制

先看一下下面这段js代码:

console.log('1');
setTimeout(function(){
    console.log('2');
},0);
console.log('3');
请问打印的结果是什么?

这段代码看似很简单,但如果不了解JavaScript运行机制就很容易答错。正确的输出是:1 3 2

一:javascript 运行机制

想要弄懂javascript执行机制(运行机制),首先要了解关于js相关的知识点,如下:

  1、理解JavaScript是单线程的概念

    JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。JavaScript的单线程与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户交互以及操作DOM。这就决定了它只能是单线程的,否则会带来复杂的同步问题。所以,为了避免复杂性,从一诞生开始JavaScript就是单线程,这已经成为了这门语言的核心特征,将来也不会变。

  2、 javaScript的同步任务和异步任务

    单线程就意味着,所有任务需要排队。前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就得一直等着。JavaScript语言的设计者认识到这个问题,将所有任务分成两种:一种是同步任务(synchronous),一种是异步任务(asynchronous)。

       同步任务指的是:在主线程上排队执行的任务,只有前一个任务执行完成,才能执行后一个任务。

     异步任务指的是:不进入主线程,而进入“任务队列(task queue)”的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

     异步任务包括:定时器、网络请求、Promise、图片加载

     javaScript中同步任务和异步任务的执行机制流程如下(引用网上的一张图): 

 

      

    1、所有同步任务都在主线程上执行,形成一个执行栈。

    2、异步任务进入Event Table并注册函数。当异步任务有了运行结果时,Event Table会将这个函数移入 Event Queue

    3、主线程中任务执行完毕为空,系统就会按次序的读取Event Queue中异步任务,进入执行栈,开始执行

    4、主线程不断重复上面的第三步。因为这个过程是循环不断的,所以又称为事件循环(Event Loop).

   3、javaScript的宏任务和微任务

      JavaScript的除了同步和异步任务外,对其任务还有更细的定义:宏任务(macro-task)和微任务(micro-task)。

      宏任务

          1. 宏任务是浏览器规定的:包括整体代码script,setTimout,setInterval、Ajax、DOM事件

          2. DOM渲染后触发(浏览器为了是js内部的宏任务于DOM任务能够有序的执行,会在一个宏任务执行结束后,在下一个宏任务执行开始前,对页面进行重新渲染)

 

      微任务

          1. 微任务是ES6语法规定的 包括:Promise、async/await

          2.DOM渲染前触发(微任务会在当前任务结束后,即主线程为空后,会立即执行所有的微任务,执行完后再进行DOM渲染)

      整体代码(宏任务)执行完后(即主线程执行栈为空),就开始事件循环(上面提到的Event Loop)。每次循环都要看Event Queue中是否有微任务,如果有则执行完所有的微任务,然后尝试DOM渲染(如果DOM需要渲染的情况),最后再执行其中一个宏任务,一个宏任务执行完成后开始进行下一次的循环。下一次循环开始先执行所有的微任务然后再执行其中一个宏任务。依次循环下去。

        javaScript中宏任务和微任务的执行机制流程如下:

         

   

  4、异步任务有哪些?放入任务队列(或者事件队列)的时机

    任务分为宏任务(macro)和微任务(micro),在任务队列中的执行顺序:微任务优先于宏任务

    异步微任务:

      1.ES6中的Promise

        放入任务队列的时机:异步操作成功(或者失败)后,将成功的回调函数(后者失败的回调函数)放入

        放入任务队列的任务:将成功的回调函数(后者失败的回调函数)放入任务队列中

        举例说明:

new Promise(function(resolve,reject){
   resolve();
}).then(function(){
   console.log('3')
});

      new Promise()立即执行,then回调函数 function(){console.log('3')} 放到Event Table中注册函数,当resolved()后,回调函数会放到微任务 Event Queue中,等待进入主线程执行

    异步宏任务:

      1.定时器(setTimeout和setInterval)

        放入Event Queue 时机:在规定时间到达后

        放入Event Queue 的任务:将回调函数放入宏事件队列中

        举例说明:     

setTimeout(fn,5000)

       fn进入Event Table,开始计时,等到5秒后回调函数进入Event Queue.当主线程代码为空时,就会读取Event Queue 中的回调函数,进入主线程开始执行。

      2.Ajax异步请求

        放入Event Queue的时机:在axjax加载完成时

        放入Event Queue 的任务: 将回调函数放入宏任务事件队列中

        举例说明:  

$.ajax({
        url:'',
        data:{},
        success:function(){
            console.log('发送成功')
        }
    })

        ajax进入Event Table,当ajax请求成功后,回调函数success进入Event Queue 。当主线程代码为空时,就会读取Event Queue 中的success回调函数,进入主线程开始执行。

      3.DOM事件

        放入Event Queue 的时机:在用户操作事件完成后

        放入Event Queue 的任务: 将回调函数放入宏任务事件队列中

        举例说明:

ele.on('click',fn,false);

         fn 进入Event Table,当触发click事件后,会将fn放入Event Queue中。

总结 javascript 运行机制:

  1. 同步代码,一行一行放在 Call Stack(调用栈) 执行

  2. 遇到异步代码(定时、网络请求等),会交给浏览器Web API 处理 ,等待时机。时机到了,就会移动到 Callback Queue(回调队列)

  3. 遇到异步微任务代码(Promise.then、async await 等),会把微任务的回调函数放到微任务队列中(macro task queue)

  4、如调用栈Call Stack 为空(即同步代码执行完),会立即执行所有微任务

  5.  微任务执行完后,会尝试DOM渲染

  6、渲染完成后会查找 事件队列 Callback Queue,如有等待的任务,则取出一个宏任务移动到 Call Stack执行

  7、然后继续4到6步骤轮询查找,也就是事件循环

分析下面代码,看看是否掌握了JS的执行机制

    例1:

    console.log('1');
    setTimeout(function(){
        console.log('2');
    },1000);
    new Promise(function(resolve){
        console.log('3');
        resolve();//Promise没有写resolve,then回调函数不会执行,为了测试直接执行
    }).then(function(){
        console.log('4');
    })
    console.log('5');

     分析:

      * 整体script作为第一个宏任务进入主线程,遇到console.log,输出1.

      * 遇到setTimeout, setTimeout是异步任务,被放到了event table 。1s 中中后,将其回调函数放到宏任务Event Queue 中。我们暂且记为setTimeout1

      *遇到Promise,  new Promise是同步任务,直接 输出3; .then里的函数是异步任务,被放到了event table。当执行resolve后将then回调函数放到微任务Event Queue。我们暂且记为then1

      * 遇到console.log('5'),直接 输出5

      * 到此主线程的任务执行完毕,第一轮事件循环宏任务结束。开始读取Event Queue中的事件,我们发现有一个微任务then1,所以先读取then1,输出4 。到此第一轮事件循环正式结束。

      *  第二轮事件循环从setTineout1开始, 输出2。第二轮事件循环结束。

      所以结果是:1 -> 3 -> 5 -> 4 -> 2 

    例2:

console.log('1');
setTimeout(function(){
    console.log('2');
    new Promise(function(resolve){
        console.log('4');
        resolve();
    }).then(function(){
        console.log('5');
    })
})

new Promise(function(resolve){
    console.log('7');
    resolve();
}).then(function(){
    console.log('8');
})

setTimeout(function(){
    console.log('9');
    new Promise(function(resolve){
        console.log('11');
        resolve();
    }).then(function(){
        console.log('12');
    })
})
console.log('13');

 

    分析:

      1. 第一轮事件循环流程:

        * 整体script作为第一个宏任务进入主线程,遇到console.log('1'),直接输出1

        * 遇到setTimout,将其回调函数放到宏任务Event Queue中。我们暂且几位setTimeout1.

        * 遇到Promise。new Promise直接执行,输出7。then回调函数被放到微任务Event Queue中,我们暂且记为then1

        * 遇到setTimeout,将其回调函数放到宏任务Event Queue中。我们暂且记为setTimeout2

        * 遇到console.log('13'),直接输出 13

        * 到此第一轮事件循环宏任务结束。此时Event Queue中的任务情况: 宏任务: setTimeout1 setTimeout2;微任务:then1

        *  开始执行事件队列上的所有微任务,我们发现只有then1一个微任务,所以直接执行then1,输出8

        * 到此,第一轮事件循环正式结束。

      2. 第二轮事件循环流程:

        * 从事件队列中的宏任务setTimeout1开始执行,直接输出2

        * 遇到promise, new Promise直接执行,输出4。 then回调函数被放微任务Event Queue中,记为 then2

        * 到此第二轮事件循环宏任务结束。此时Event Queue中的情况: 宏任务:setTimeout2 ;微任务:then2

        *  开始执行event Queue中的所有微任务,发现只有then2一个微任务。直接执行,输出5

        * 到此,第二轮事件循环正式结束

      3. 第三轮事件循环流程:

        * 宏任务只剩下setTimeout2了,直接输出9.

        * 遇到promise ,new Promise 直接执行,输出11。 将then回调函数放到微任务Event Queue中,记为then3

        * 第三轮事件循环宏任务结束,开始执行微任务then3,输出 12

         * 第三轮事件循环结束

      3. 整段代码共进行了三次事件循环,输出:1 -> 7 -> 13 ->8 -> 2 ->4->5 -> 9 ->11->12

    

 

    

posted @ 2019-04-13 18:33  yangkangkang  阅读(208)  评论(0编辑  收藏  举报