JavaScript是一门单线程语言,一切JavaScript中的“多线程”都是用单线程模拟出来的,而这主要是靠事件循环机制来实现的javascript
JS引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就回去Event Queue那里检查是否有等待被调用的函数。前端
let data = {};
$.ajax({
url: www.javascript.com,
data: data,
success: () => {
console.log("发送成功!");
}
})
console.log("同步代码执行结束");
复制代码
success
console.log("同步代码执行结束");
success
进入Event Queue。success
并执行setTimeout
这个函数是通过指定时间后,把要执行的任务,加入到Event Queue中。又由于主线程任务是一个个执行,若是前面的任务执行须要的时间太长,那么Event Queue中的任务只能等着,因此可能会致使setTimeout
要执行的任务真正的延迟时间会大于指定时间。java
setTimeout(() => {
task()
},3000)
sleep(10000000)
复制代码
task
进入Event Table并注册,计时开始sleep
函数,很慢,很是慢,计时还在继续timeout
完成,task
进入Event Queue,可是由于sleep
执行得太慢了,还没执行完,只好等着sleep
终于执行完了,task
终于从Event Queue进入到主线程中执行setTimeout(fn, 0) // 指定某个任务在主线程执行栈为空后立刻执行,但根据HTML的标准,实际不可能达到0毫秒,最低是4毫秒
复制代码
setInterval
与setTimeout
相似,不一样点在于,setInterval
是循环的执行。它会每隔指定的时间,将注册的函数置入Event Queue,若是前面的任务耗时过久,那么一样须要等待。 对于setInterval(fn, ms)
来讲,不是每过ms秒会执行一次fn
,而是每过ms秒,会有fn
进入Event Queue。一旦setInterval
的回调函数fn
执行时间超过了延时时间ms,那么就彻底看不出来有时间间隔了node
除了广义的同步任务和异步任务,还有对任务有更精确的定义ajax
process.nextTick
相似于node版的setTimeout,在下次循环的下一次循环中调用callback回调函数promise
不一样类型的任务会进入对应的Event Queue,例如setTimeout
和setInterval
会进入相同的Event Queuebash
事件循环的顺序,决定JS代码的执行顺序。进入总体代码(宏任务)后,开始第一次循环。接着执行这一次循环所产生的全部微任务。而后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行全部的微任务。多线程
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
复制代码
setTimeout
,那么将其回调函数注册后分发到宏任务Event Queue。(注册过程与上同,下文再也不描述)Promise
,new Promise
当即执行,并将then
函数分发到微任务Event Queue。then
在微任务Event Queue里面,执行。题1:异步
async function async1() {
console.log(1);
const result = await async2();
console.log(3);
}
async function async2() {
console.log(2);
}
Promise.resolve().then(() => {
console.log(4);
});
setTimeout(() => {
console.log(5);
});
async1();
console.log(6);
复制代码
答案: [1,2,6,4,3,5]async
then
函数分发到微任务Event QueuesetTimeout
,将其回调函数注册后分发到宏任务Event Queueasync1
函数并执行,输出1async2
输出2,await意味着后面的代码要等等了console.log(3)
是在async2
函数返回的Promise的then
函数中执行的,因此讲它分发到微任务Event Queue题2:
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]
第一轮事件循环流程分析以下:
console.log
,输出1。setTimeout
,其回调函数被分发到宏任务Event Queue中。咱们暂且记为setTimeout1
。process.nextTick()
,其回调函数被分发到微任务Event Queue中。咱们记为process1
。Promise
,new Promise
直接执行,输出7。then
被分发到微任务Event Queue中。咱们记为then1
。setTimeout
,其回调函数被分发到宏任务Event Queue中,咱们记为setTimeout2
宏任务Event Queue | 微任务Event Queue |
---|---|
setTimeout1 | process1 |
setTimeout2 | then1 |
process1
和then1
两个微任务。process1
,输出6。then1
,输出8。好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1
宏任务开始:
process.nextTick()
,一样将其分发到微任务Event Queue中,记为process2
。new Promise
当即执行输出4,then
也分发到微任务Event Queue中,记为then2
。宏任务Event Queue | 微任务Event Queue |
---|---|
setTimeout2 | process2 |
then2 |
process2
和then2
两个微任务能够执行。setTimeout2
了,执行。process.nextTick()
分发到微任务Event Queue中。记为process3
。new Promise
,输出11。then3
。宏任务Event Queue | 微任务Event Queue |
---|---|
process3 | |
then3 |
process3
和then3
。整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。 (请注意,node环境下的事件监听依赖libuv与前端环境不彻底相同,输出顺序可能会有偏差)