引言
- javascript是一门单线程的脚本语言。单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务。所以,在这里就会牵扯到一个先后顺序的问题,即js循环机制。
js调用栈
- 先进后出,调用时,添加顶部,执行完成之后就从栈顶移出该函数
同步任务
- 调用栈按顺序等待主线程执行
异步任务
- 异步有了结果将回调函数添加到任务队列,任务队列等主线程空闲
宏任务
- (macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染。
宏任务包括
script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)
微任务
- microtask,可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。
微任务包括
await
Promise.then
Object.observe
MutationObserver
process.nextTick(Node.js 环境)--当页面元素被重新渲染,一个宏任务结束,下一个宏任务开始前
事件每循环一次叫做tick,关键步骤:
- Event Loop开始时,会从代码开始一行一行执行,遇到函数调用会压入调用栈中,被压入的函数叫做帧(Frame),当函数返回后从调用栈中弹出整个操作栈被清空。
- javascript中的异步操作,比如事件回调,settimeout,setInterval中的回调函数会入队到消息队列中,成为消息。
- 消息在调用栈清空的时候进行,消息队列中的消息被压入调用栈中
- 微任务队列会在调用栈被清空时立即执行并且处理期间新加入的微任务也会一同执行。
总结:
先整体扫,在局部微任务跟在当前宏任务后面,执行完当前宏任务,微任务就跟上,这就是一次循环。然后再执行下一个宏任务。
代码:
after时行内元素,设置display或者设置定位
console.log('1')1;//1.压入调用栈,输出1
setTimeout(function () {//2.回调函数加入到任务队列里
console.log('2');//16.
Process.nextTick(function () {
console.log('3');
})
new Promise(function (resolve) {
console.log('4');//17
resolve();//18
}).then(function () {
console.log('5');//18
})
})
//在这里执行Process.nextTick--打印6
// process.nextTick(function () {
// console.log('6');
// })
new Promise(function (resolve) {//3.压入调用栈
console.log('7');//4.输出7
resolve();//5.执行resolve函数
}).then(function () {//13.then的回调函数进入微任务队列中
console.log('8');
})
setTimeout(function () {//6.将回调函数压入到消息队列中
console.log('9');//19
// process.nextTick(function () {
// console.log('10');
// })
new Promise(function (resolve) {
console.log('11');//20
resolve();//21
}).then(function () {
console.log('12');//22
//在这里执行Process.nextTick--打印3
})
})
console.log('0');//7.压入到调用栈中,输出
setTimeout(()=>{//8.将回调函数压入到消息队列中
console.log('1');//23
new Promise(resolve => {
console.log('2');//24
resolve();//25
// 这块没有调用resolve就不会执行下面的.then
}).then(()=> {
console.log(3);
//在这里执行Process.nextTick--打印10
})
})
new Promise(resolve => {//9.压入调用栈
console.log(4);//10.输出
for (let i= 0;i<9;i++)//11.执行for循环
{
i==7&&resolve();
}
console.log(5);//12.输出
}).then(()=>{//14.执行then函数
console.log(6);
}
)
扩展:
1.浏览器是多进程的
浏览器内核有多个线程在工作,最终都会等待js引擎线程来响应
- GUI渲染线程
- js引擎线程
- http请求线程
- 事件触发线程
- 定时器触发线程
2.定时器时间不准确的原因
- 定时器会开启一条定时器触发器线程来计时,在等待了指定的时间后将事件放入到任务队列中等待主线程执行
- 延迟毫秒数并不准确:到了指定的时间将事件放入到任务队列中,要等到同步任务和现有任务队列中事件全部执行完成以后,才会去读取定时器事件到主线程执行。