JavaScript单线程起源:
JavaScript作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM,为了避免复杂性,诞生开始,JavaScript就是单线程语言。
比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程。
单线程及存在的问题:
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
问题的解决--同步、异步
synchronous(同步任务)和asynchronousk(异步任务)
- 同步任务是调用立即得到结果的任务,同步任务在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
- 异步任务是调用无法立即得到结果的任务,需要额外的操作才能预期结果的任务,异步任务不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
macro-task(宏任务)和micro-task(微任务)
- 宏任务:macro-task 可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行,每一个宏任务会从头到尾将这个任务执行完毕,不会执行其它)包括整体代码script,setTimeout,setInterval等
微任务:micro-task可以理解是在当前 task 执行结束后立即执行的任务 包括Promise,process.nextTick等
Event Loop事件循环
Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,其实宏任务队列和微任务队列的执行,就是事件循环的一部分。
事件循环的具体流程如下:
- 从宏任务队列中,按照入队顺序,找到第一个执行的宏任务,放入调用栈,开始执行;
- 执行完该宏任务下所有同步任务后,即调用栈清空后,该宏任务被推出宏任务队列,然后微任务队列开始按照入队顺序,依次执行其中的微任务,直至微任务队列清空为止;
- 当微任务队列清空后,一个事件循环结束;
- 接着从宏任务队列中,找到下一个执行的宏任务,开始第二个事件循环,直至宏任务队列清空为止。
Js执行机制流程图
注意:
- 当我们第一次执行的时候,解释器会将整体代码script放入宏任务队列中,因此事件循环是从第一个宏任务开始的;因此我在流程图中将主线程单独分离开。
- 如果在执行微任务的过程中,产生新的微任务添加到微任务队列中,也需要一起清空;微任务队列没清空之前,是不会执行下一个宏任务的。
流程图解释:
- 在Js任务入栈后判断是否为同步任务,同步任务则立即执行;若为异步任务则判断微任务或宏任务加入相应队列等待处理。
- 在所有同步进程执行结束,立即执行当前微任务队列中的全部微任务(因为整体的Js代码视为一个宏任务)
- 当微队列清空后,执行宏任务队列中的下一个宏任务,执行结束后,判断微任务队列是否存在微任务,存在的话,则清空微任务队列,开启下一个循环。
Event Loop练习
console.log("a");
setTimeout(function () {
console.log("b");
}, 0);
new Promise((resolve) => {
console.log("c");
resolve();
})
.then(function () {
console.log("d");
})
.then(function () {
console.log("e");
});
console.log("f");
/**
* 输出结果:a c f d e b
*/
async function async1() {
console.log("a");
const res = await async2();
console.log("b");
}
async function async2() {
console.log("c");
return 2;
}
console.log("d");
setTimeout(() => {
console.log("e");
}, 0);
async1().then(res => {
console.log("f")
})
new Promise((resolve) => {
console.log("g");
resolve();
}).then(() => {
console.log("h");
});
console.log("i");
/**
* 输出结果:d a c g i b h f e
*/
这段代码中声明了两个async方法,
- 在整体的Script语句中,首先输出:d;
setTimeout中的语句加入宏任务队列;
宏任务队列 微任务队列 整体的Script setTimeout - async1()执行,输出:a;后async2()执行,输出:c;await阻断执行;console.log("b")暂不执行,而去执行主线程任务;
此时async1()未执行结束,.then()方法不会被加入微任务队列;
宏任务队列 微任务队列 整体的Script setTimeout 执行new Promise输出:g;.then(() => {console.log("h");})加入微任务队列;
宏任务队列 微任务队列 整体的Script console.log("h") setTimeout 6.执行console.log("i");语句,输出:i;此时主线程的同步任务已经全部执行结束,返回阻断处继续执行;输出:b;.then(res => {console.log("f")})加入微队列;
宏任务队列 微任务队列 setTimeout console.log("h") console.log("f") 7.清空微任务队列后,执行宏任务队列的下一个任务,开启下一个循环;输出h、f、e;
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
/**
* 输出结果:1,7,6,8,2,4,3,5,9,11,10,12(node与浏览器可能略有不同)
*/
注:
- 在node中,process.nextTick()优先级高于Promise.then、catch、finally;
- 在node中,宏任务队列:setImmediate()队列优先级高于setTimeout()队列;
- 在node14以后,也是每执行一个宏任务、则清空即时的微任务队列。
参考文档:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。