js的执行阶段
网页的线程
JS引擎线程: 也称为JS内核,负责解析执行Javascript脚本程序的主线程(例如V8引擎)
事件触发线程: 归属于浏览器内核进程,不受JS引擎线程控制。主要用于控制事件(例如鼠标,键盘等事件),当该事件被触发时候,事件触发线程就会把该事件的处理函数推进任务队列
,等待JS引擎线程执行
定时器触发线程:主要控制计时器setInterval和延时器setTimeout,用于定时器的计时,计时完毕,满足定时器的触发条件,则将定时器的处理函数推进任务队列
中,等待JS引擎线程执行。 注:W3C在HTML标准中规定setTimeout低于4ms的时间间隔算为4ms。
HTTP异步请求线程:通过XMLHttpRequest连接后,通过浏览器新开的一个线程,监控readyState状态变更时,如果设置了该状态的回调函数,则将该状态的处理函数推进任务队列
中,等待JS引擎线程执行。 注:浏览器对通一域名请求的并发连接数是有限制的,Chrome和Firefox限制数为6个,ie8则为10个。
注意:永远只有JS引擎线程在执行JS脚本程序,其他三个线程只负责将满足触发条件的处理函数推进任务队列
,等待JS引擎线程执行, 不参与代码解析与执行。
注意:setTimeout和setInterval浏览器给他们开创了一个延迟队列,当事件到了才放入任务队列
宏任务(task)
一个任务就是指计划由标准机制来执行的任何 JavaScript,如程序的初始化、事件触发的回调等。 除了使用事件,你还可以使用 setTimeout() 或者 setInterval() 来添加任务
这里的MDN中的任务我个人认为就是我们所说的宏任务
script (主代码块)、setTimeout
、setInterval
、setImmediate
、I/O 、UI rendering
宏任务(macro-task)可分为同步任务和异步任务:
理解宏任务中同步任务和异步任务的执行顺序,那么就相当于理解了JS异步执行机制–事件循环(Event Loop)。
事件循环(Event Loop)
事件循环可以理解成由三部分组成,分别是:
任务队列进入执行栈是一个一个进入的
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
浏览器端运行结果:timer1 => promise1 => timer2 => promise2
课外知识点:
setTimeout和setInterval的区别:
setTimeout(
() => {
console.log('1')
let arr = []
for(let i = 0; i < 100000000; i++) {
arr[i] = i
}
},
1000)
setTimeout(() => {
console.log('3')
},1000)
// 输出1 3 是由时间间隔的 因为处理第一个定时器是要时间的
微任务
process.nextTick
(Nodejs) 、promise
、Object.observe
、MutationObserver
微任务是在es6和node环境中出现的一个任务类型,如果不考虑es6和node环境的话,我们只需要理解宏任务事件循环的执行过程就已经足够了,但是到了es6和node环境,我们就需要理解微任务的执行顺序了。 微任务(micro-task)的API主要有:Promise, process.nextTick
微任务通常来说就是需要在当前 task 执行结束后立即执行的任务
例题
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
解析
-
代码块通过语法分析和预编译后,进入执行阶段,当JS引擎主线程执行到console.log('script start');,JS引擎主线程认为该任务是同步任务,所以立刻执行输出script start,然后继续向下执行;
-
JS引擎主线程执行到setTimeout(function() { console.log('setTimeout'); }, 0);,JS引擎主线程认为setTimeout是异步任务API,则向浏览器内核进程申请开启定时器线程进行计时和控制该setTimeout任务。由于W3C在HTML标准中规定setTimeout低于4ms的时间间隔算为4ms,那么当计时到4ms时,定时器线程就把该回调处理函数推进任务队列中等待主线程执行,然后JS引擎主线程继续向下执行
-
JS引擎主线程执行到Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); });,JS引擎主线程认为Promise是一个微任务,这把该任务划分为微任务,等待执行
-
JS引擎主线程执行到console.log('script end');,JS引擎主线程认为该任务是同步任务,所以立刻执行输出script end
-
主线程上的宏任务执行完毕,则开始检测是否存在可执行的微任务,检测到一个Promise微任务,那么立刻执行,输出promise1和promise2
-
微任务执行完毕,主线程开始读取任务队列中的事件任务setTimeout,推入主线程形成新宏任务,然后在主线程中执行,输出setTimeout
注意
1.明明说宏任务包含了异步任务.也就是异步Ajax,DOM事件,setTimeout等。那为什么上面例子中的setTimeout不是在宏任务中执行的?
原因:宏任务其实就是执行执行栈中的任务,之所以说宏任务会执行(异步Ajax,DOM事件,setTimeout),那是因为在第一次执行script代码块的时候(异步Ajax,DOM事件,setTimeout),通过相关引擎被推入了任务队列中,在第一次宏任务执行完,继续执行微任务后,接着在执行任务队列的时候(异步Ajax,DOM事件,setTimeout)这里就会从任务队列推送到执行栈中去执行了
2.事件循环是一个连续的过程
console.log(1);
console.log(2);
setTimeout(function(){
console.log(3);
setTimeout(function(){
console.log(6);
})
})
setTimeout(function(){
console.log(4);
setTimeout(function(){
console.log(7);
})
})
console.log(5)
同样,先执行栈里的同步代码 1 2 5. 再同样,在第一次执行宏任务最外层的settimeout会放在任务队列里,当执行栈里面执行完成以后,任务队列会推送到栈中执行,3 4。 而嵌套的2个settimeout,会在这一次执行执行栈执行的过程中放在一个新的任务队列中,最后去执行 6 7.
文章参考:GitHub - yygmind/blog: 我是木易杨,公众号「高级前端进阶」作者,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!
例子
多微任务情况
<script>
async function test () {
console.log(3)
await new Promise(resolve => resolve())
console.log(4)
await new Promise(resolve => resolve())
console.log(5)
await new Promise(resolve => resolve())
console.log(6)
}
new Promise(resolve => resolve()).then(() => {console.log(2)})
setTimeout(() => {
console.log('7')
}, 0)
console.log(1)
test()
// 1 3 2 4 5 6 7
</script>
由于 await 会产生一个promise的一个微任务队列,就相对与在后面加了给then
当移除 async 和 await的时候
function test() {
console.log(3)
new Promise(resolve => resolve())
console.log(4)
new Promise(resolve => resolve())
console.log(5)
new Promise(resolve => resolve())
console.log(6)
}
new Promise(resolve => resolve()).then(() => { console.log(2) })
setTimeout(() => {
console.log('7')
}, 0)
console.log(1)
test()
// 1 3 4 5 6 2 7
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)