JS执行机制

2018-12-23  本文已影响24人  0爱上1

引言

众所周知,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前端每日干货
上一篇下一篇

猜你喜欢

热点阅读