美文网首页
JS执行机制

JS执行机制

作者: 0爱上1 | 来源:发表于2018-12-23 11:20 被阅读24次

    引言

    众所周知,JS自出生之日起就被设定为了一门单线程的语言,所谓单线程就是指任务执行的顺序需要一个接一个,如果其中某一个任务执行的时候需要占用大量的时间(比如加载高清图片,执行IO等),那么后面的任务就必须等着,如果打开一个含有高清图片的网页,内容必须等着图片加载完才能显示的话,那浏览该网页的人估计得骂娘了...

    如何解决

    基于以上场景,JS中诞生了一种叫做事件循环的机制,用于解决这种同步导致的堵塞问题

    JS在执行时也是有类似Java的执行栈的,被执行的函数一个一个的被推入执行栈执行,如果执行栈中的函数超过了栈所能承载的容量,也是会发生栈内存溢出的

    EventLoop

    一种事件循环机制,用以解决JS中异步执行任务的方式

    首先大概描述一下EventLoop的执行机制

    1. 当一个网页被加载时,有同步执行任务也有需要异步执行的任务(耗时大的),JS脚本执行顺序自上而下,当遇到同步任务时,同步任务就会进入主线程的执行栈中按照顺序执行
    2. 一旦遇到异步任务,该异步任务就会进入EventLoop的EventTable事件表中注册好该异步函数,
    3. 等到该异步任务执行完毕后,EventTable中的函数就会进入EventLoop的EventQueue任务队列中
    4. 当主线程内的同步任务执行完毕为空的时候,主线程会去Event Queue读取对应的函数,进入主线程执行

    代码示例

    // 编写以下代码
    console.log('start');
    setTimeout(() => {console.log('setTimeout');}, 3000 );
    console.log('end');
    
    // 结果
    start
    end
    setTimeout
    

    流程解析

    1. 执行 console.log('start'); 打印出start
    2. 执行 setTimeout函数,发现是异步任务,故setTimeout进入到EventLoop的EventTable中并注册回调函数() => {console.log('setTimeout');},
    3. 执行 console.log('end'); 打印出end
    4. 当setTimeout任务执行结束,回调函数会进入到EventLoop的EventQueue中
    5. 主线程栈为空时,会去EventQueue中获取回调函数,将其放入主线程的执行栈中执行

    注意一下就是setTimeout函数的第二个参数,是指时间到了之后该函数的第一个参数(回调函数)会进入EventLoop的EventQueue中,具体什么时候会被执行需要等主线程的执行栈为空时,也就是说,这里设置的定时时间是不准的

    另一点需要注意的是setTimeout定时任务到底由谁执行?怎么知道三秒钟时间到了的呢?其实这里是JS将该异步任务交给了其宿主环境(浏览器),浏览器中有一个负责执行setTimeout的时钟线程,等时间到了,就会通知EventLoop将回调函数放入EventQueue中了


    以上就是JS的执行顺序,下面说一下JS异步任务的分类

    前天看了公司同事的一篇文章,才知道原来JS异步任务也分为宏和微之说,这两天就学习了一下,做个自我总结

    宏任务与微任务

    1. 不论是宏任务还是微任务皆属于JS异步任务范畴,只是它们的在EventLoop中的走向与取值不同,也就是执行顺序不同
    2. 另一点就是同类型的任务会进入到同类型的队列中
    宏任务

    宏任务一般包括:整体Script代码,setTimeout,setInterval以及setImmediate

    微任务

    微任务一般包括:Promise,process.nextTick,MutationObserver

    下面用一个代码示例去分析它们二者的区别到底在哪里

    代码示例
    // 编写一下代码
    setTimeout(()=>{
      console.log('setTimeout')
    },0)
    let p = new Promise((resolve,reject)=>{
      console.log('Promise1')
      resolve()
    })
    p.then(()=>{
      console.log('Promise2')    
    })
    
    // 执行结果
    Promise1
    Promise2
    setTimeout
    

    简单分析一下执行流程

    1. setTimeout函数为一个宏任务,即会在4ms后进入宏任务队列,等待主线程为空后调用
    2. new Promise中的console.log('Promise1')为同步任务,主线程会直接执行并输出 Promise1
    3. p.then为异步微任务,会进入到微任务队列中
    4. 主线程会在同步任务执行完后会去清空微任务queues中的所有微任务,这里输出了 Promise2
    5. 清空完所有微任务主线程再去宏任务队列取宏任务执行 ,输出了setTimeout

    下面再看一个复杂一点的微宏任务,对于理解他们的优先级会更有帮助

    // 编写以下代码
    Promise.resolve().then(()=>{ // 微任务1 v1
      console.log('Promise1')  
      setTimeout(()=>{
        console.log('setTimeout2')
      },0)
    })
    
    setTimeout(()=>{ // 宏任务1 h1
      console.log('setTimeout1')
      Promise.resolve().then(()=>{
        console.log('Promise2')    
      })
    },0)
    
    // 执行结果
    Promise1
    setTimeout1
    Promise2
    setTimeout2
    

    下面分析具体的执行流程

    1. 执行微任务1,微任务1进入EventLoop的微任务队列中(取名为v1),待主线程为空时执行

    2. 执行宏任务1,宏任务1进入EventLoop的宏任务队列中(取名为h1),待主线程为空时情况完所有微任务队列后执行宏任务

    3. 主线程此时为空,去微任务队列中获取微任务,此时微任务只有v1,即输出了Promise1,输出之后又执行v1中的setTimeout函数,生成了一个新的宏异步任务(宏任务2、取名为h2),此时宏任务队列中有两个宏任务:h1 和 h2

    4. 由于此时微任务队列空了,主线程也是空的,故会去宏任务队列中获取宏任务执行,又由于h1在h2之前先进入宏队列,故主线程先获取到h1宏任务执行,输出了setTimeout1,紧接着在setTimeout1输出后宏任务h1内部又生成了一个新的微任务v2,并将v2放入微任务队列中

    5. 由于微任务队列中又有了新的微任务,故主线程在空的情况下会去微任务队列中清空所有微任务,这时候执行的是v2,输出了Promise2

    6. 当微任务队列为空时,主线程亦为空,故主线程会去宏任务队列中获取宏任务执行,此时宏任务队列中还有一个h2,故最后输出了setTimeout2

    总结

    以上就是我对JS执行机制以及微,宏异步任务的理解,最后感谢不厌其烦教我的美女同事小Z,奉上她的公众号,大家可以关注一下

    web前端每日干货

    相关文章

      网友评论

          本文标题:JS执行机制

          本文链接:https://www.haomeiwen.com/subject/vcmgkqtx.html