1、JS为什么是单线程
js的代码只能在一个线程上运行,也就说,js同时只能执行一个js任务,js的主要用途是与用户互动和操作DOM。如果一段JS代码并行在两个线程上运行,一个线程在DOM上添加内容,另一个线程在删除DOM,就会存在操作冲突,所以为了避免复杂性,JS选择单线程执行
2、浏览器的多线程
•js引擎线程:用于解释执行js代码、用户输入、网络请求、运行JS代码的线程,叫做主线程,同步的代码直接在主线程运行,异步代码交给其他线程运行
•GUI渲染线程:绘制用户界面,与JS主线程互斥(因为js可以操作DOM,进而会影响到GUI的渲染结果);
•http异步网络请求线程:处理用户的get、post等请求,等返回结果后将回调函数推入到任务队列;
•定时触发器线程:setInterval、setTimeout等待时间结束后,会把执行函数推入任务队列中;
•浏览器事件处理线程:将click、mouse等UI交互事件发生后,将要执行的回调函数放入到事件队列中。
虽然异步的逻辑交给其他线程去执行,但是最终所有的回调还是会回到主线程完成执行~
3、同步任务和异步任务
JS在执行过程中会产生两种任务,分别是:同步任务和异步任务;
•同步任务:比如声明语句、for、赋值等,读取后依据从上到下从左到右,立即执行。
•异步任务:比如 ajax 网络请求,setTimeout 定时函数等都属于异步任务。异步任务会通过任务队列(Event Queue)的机制(先进先出的机制)来进行协调。
那么异步任务是怎么由任务队列协调回到主线程执行的呢?我们继续往下看~
4、任务队列
可以理解为一个静态的队列存储结构,非线程,只做存储,里面存的是一堆异步成功后的回调函数,先成功的异步的回调函数在队列的前面,后成功的在后面。
注意:异步成功后,才把其回调函数扔进队列中,而不是一开始就把所有异步的回调函数扔进队列
执行流程:下图流程解析
1.主线程执行所有同步任务,异步任务交给其他线程执行
2.其他线程执行完异步任务,将回调函数放入任务队列中
3.主线程任务执行完毕,读取任务队列中是否有任务要执行,有则主线程开始执行异步任务
4.主线程会不断重复第3个流程
我们看到任务队列中包含了多种异步任务回调函数,这些任务是否有执行顺序的区分呢?继续往下看
5、事件循环
任务队列中的任务也分为两种,分别是:宏任务(Macro-take)和微任务(Micro-take),这两个任务分别维护一个队列,都是采用先进先出的策略进行执行。
•宏任务主要包括:scrip(JS 整体代码)、setTimeout、setInterval、setImmediate、UI 交互
•微任务主要包括:Promise(重点关注)、process.nextTick(Node.js)、MutationObserver(Dom监听)
1.异步任务会区分宏任务、微任务类型,分别放到对应的任务队列,两个任务队列均遵循先进先出的机制
2.主线程执行完同步任务会检查任务队列中是否有未完成的微任务,有则执行全部的微任务,执行完之后继续下一个宏任务
3.主线程会不断地读取任务队列中的任务,循环2流程
主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)
浏览器为了能够使宏任务和DOM渲染有序的进行,会在一个宏任务执行后,查看是否有微任务需要执行,执行完所有微任务之后,GUI渲染线程开始工作,对页面进行渲染;也就是当一个宏任务执行完会立刻执行一个微任务,然后进行页面渲染
微任务 -> GUI渲染 -> 宏任务 -> ...
为了更好的理解任务的执行顺序,我们看以下两个示例解析
示例1:
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");
执行顺序:
1.先执行同步任务:输出script start
2.遇到异步任务setTimeout,放入宏任务队列
3.遇到Promise,加入微任务队列(两个回调promise1、promise2)
4.同步代码执行:输出script end
5.同步代码执行完毕,执行微任务队列:输出promise1,then 之后产生一个微任务,加入微任务队列(promise2)
6.执行微任务,输出: promise2
7.微任务:执行微任务队列(promise1)
8.执行渲染操作,更新界面
9.执行宏任务,输出:setTimeout
示例2:
new Promise((resolve, reject) => {
resolve(1)
new Promise((resolve, reject) => {
resolve(2)
}).then(data => {
console.log(data)
})
}).then(data => {
console.log(data)
})
console.log(3)
执行顺序:
1.先同步任务new Promise,没有打印,执行下一个同步任务new Promise,还是没有打印
2.执行.then,异步微任务,放入微任务队列
3.执行外部的.then,异步微任务放入微任务队列
4.同步任务,打印3
5.执行微任务,先执行内部的回调输出2,在执行下一个微任务,打印外部的回调输出1