一句话解释
js的主线程是单线程运行的,主线程先执行同步代码,遇到异步操作如ajax请求、定时器等需要在一段时间后再执行的事件,便交给其他线程如请求线程、定时触发线程,满足条件后就放到任务队列中,主线程执行完任务后,就去任务队列里拉取任务,按照先进先出的原则依次执行,如此循环;
其中任务队列分为宏任务队列与微任务队列,执行完一个宏任务都会去检查是否有微任务,执行完所有微任务后,页面更新渲染,再执行下一个宏任务;
问答
1.为什么JS是单线程的?
- 防止同时操作同一DOM出现混乱。
- 所谓的"JS是单线程的"只是指JS的主运行线程只有一个,而不是整个运行环境都是单线程。
2.JS单线程如何实现异步?
- JS异步的实现靠的就是浏览器的多线程,当遇到异步API时,就将这个任务交给对应的线程,当这个异步API满足回调条件时,对应的线程又通过事件触发线程将这个事件放入任务队列,然后主线程从任务队列取出事件继续执行。
- JS引擎线程跟GUI线程是互斥的,一个执行就另一个不执行,如果JS长时间运行,GUI线程就不能执行,整个页面就感觉卡死了。
3.为什么要有微任务?
- 微任务是为了解决效率和实时性问题,处理高优先级的任务。
4.常见的宏任务与微任务有哪些?
- 宏任务macrotask:script(整体代码)、setTimeout、setInterval、setImmediate、I/O
- 微任务microtask:Promise、MutaionObserver、process.nextTick(Node)
5.浏览器什么时候渲染?
- 执行完一个宏任务以及当前所有微任务后渲染一次;
- 但并不是每轮event loop都会更新渲染,这取决于是否修改了dom和浏览器觉得是否有必要在此时立即将新状态呈现给用户。如果在一帧的时间内(时间并不确定,因为浏览器每秒的帧数总在波动,16.7ms只是估算并不准确)修改了多处dom,浏览器可能将变动积攒起来,只进行一次绘制。
6.与Node环境的事件循环有什么区别?
浏览器和Node 环境下,microtask 任务队列的执行时机不同:
- 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行;
- Node版本>=11,和浏览器表现一致;
- Node版本<11,microtask 在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务;
由于定时器插入时机不同,同一代码执行结果也会不同,如下:
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
Node端分为6个阶段,
- timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
- I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
- idle, prepare 阶段:仅node内部使用
- poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
- check 阶段:执行 setImmediate() 的回调
- close callbacks 阶段:执行 socket 的 close 事件回调
外部输入数据-->轮询阶段(poll)-->检查阶段(check)-->关闭事件回调阶段(close callback)-->定时器检测阶段(timer)-->I/O事件回调阶段(I/O callbacks)-->闲置阶段(idle, prepare)-->轮询阶段(按照该顺序反复运行)