JS执行机制
引言
众所周知,JS自出生之日起就被设定为了一门单线程的语言,所谓单线程就是指任务执行的顺序需要一个接一个,如果其中某一个任务执行的时候需要占用大量的时间(比如加载高清图片,执行IO等),那么后面的任务就必须等着,如果打开一个含有高清图片的网页,内容必须等着图片加载完才能显示的话,那浏览该网页的人估计得骂娘了...
如何解决
基于以上场景,JS中诞生了一种叫做事件循环的机制,用于解决这种同步导致的堵塞问题
JS在执行时也是有类似Java的执行栈的,被执行的函数一个一个的被推入执行栈执行,如果执行栈中的函数超过了栈所能承载的容量,也是会发生栈内存溢出的
EventLoop
一种事件循环机制,用以解决JS中异步执行任务的方式
首先大概描述一下EventLoop的执行机制
- 当一个网页被加载时,有同步执行任务也有需要异步执行的任务(耗时大的),JS脚本执行顺序自上而下,当遇到同步任务时,同步任务就会进入主线程的执行栈中按照顺序执行
- 一旦遇到异步任务,该异步任务就会进入EventLoop的EventTable事件表中注册好该异步函数,
- 等到该异步任务执行完毕后,EventTable中的函数就会进入EventLoop的EventQueue任务队列中
- 当主线程内的同步任务执行完毕为空的时候,主线程会去Event Queue读取对应的函数,进入主线程执行
代码示例
// 编写以下代码
console.log('start');
setTimeout(() => {console.log('setTimeout');}, 3000 );
console.log('end');
// 结果
start
end
setTimeout
流程解析
- 执行 console.log('start'); 打印出start
- 执行 setTimeout函数,发现是异步任务,故setTimeout进入到EventLoop的EventTable中并注册回调函数() => {console.log('setTimeout');},
- 执行 console.log('end'); 打印出end
- 当setTimeout任务执行结束,回调函数会进入到EventLoop的EventQueue中
- 主线程栈为空时,会去EventQueue中获取回调函数,将其放入主线程的执行栈中执行
注意一下就是setTimeout函数的第二个参数,是指时间到了之后该函数的第一个参数(回调函数)会进入EventLoop的EventQueue中,具体什么时候会被执行需要等主线程的执行栈为空时,也就是说,这里设置的定时时间是不准的
另一点需要注意的是setTimeout定时任务到底由谁执行?怎么知道三秒钟时间到了的呢?其实这里是JS将该异步任务交给了其宿主环境(浏览器),浏览器中有一个负责执行setTimeout的时钟线程,等时间到了,就会通知EventLoop将回调函数放入EventQueue中了
以上就是JS的执行顺序,下面说一下JS异步任务的分类
前天看了公司同事的一篇文章,才知道原来JS异步任务也分为宏和微之说,这两天就学习了一下,做个自我总结
宏任务与微任务
- 不论是宏任务还是微任务皆属于JS异步任务范畴,只是它们的在EventLoop中的走向与取值不同,也就是执行顺序不同
- 另一点就是同类型的任务会进入到同类型的队列中
宏任务
宏任务一般包括:整体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
简单分析一下执行流程
- setTimeout函数为一个宏任务,即会在4ms后进入宏任务队列,等待主线程为空后调用
- new Promise中的console.log('Promise1')为同步任务,主线程会直接执行并输出 Promise1
- p.then为异步微任务,会进入到微任务队列中
- 主线程会在同步任务执行完后会去清空微任务queues中的所有微任务,这里输出了 Promise2
- 清空完所有微任务主线程再去宏任务队列取宏任务执行 ,输出了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进入EventLoop的微任务队列中(取名为v1),待主线程为空时执行
-
执行宏任务1,宏任务1进入EventLoop的宏任务队列中(取名为h1),待主线程为空时情况完所有微任务队列后执行宏任务
-
主线程此时为空,去微任务队列中获取微任务,此时微任务只有v1,即输出了Promise1,输出之后又执行v1中的setTimeout函数,生成了一个新的宏异步任务(宏任务2、取名为h2),此时宏任务队列中有两个宏任务:h1 和 h2
-
由于此时微任务队列空了,主线程也是空的,故会去宏任务队列中获取宏任务执行,又由于h1在h2之前先进入宏队列,故主线程先获取到h1宏任务执行,输出了setTimeout1,紧接着在setTimeout1输出后宏任务h1内部又生成了一个新的微任务v2,并将v2放入微任务队列中
-
由于微任务队列中又有了新的微任务,故主线程在空的情况下会去微任务队列中清空所有微任务,这时候执行的是v2,输出了Promise2
-
当微任务队列为空时,主线程亦为空,故主线程会去宏任务队列中获取宏任务执行,此时宏任务队列中还有一个h2,故最后输出了setTimeout2
总结
以上就是我对JS执行机制以及微,宏异步任务的理解,最后感谢不厌其烦教我的美女同事小Z,奉上她的公众号,大家可以关注一下
web前端每日干货