javascript是一门 单线程 语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变。所以一切javascript版的"多线程"都是用单线程模拟出来的,一切javascript多线程都是纸老虎!
javascript 上任务分为两种,分别为同步任务和异步任务。
在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行
在掘金上盗了一张图
** 导图要表达的内容用文字来表述的话:**
js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。
$.ajax({
url:'xxx',
data:{},
success: (res)=>{
console.log('发送成功');
}
});
console.log('代码执行结束');
// 代码执行结束 发送成功
** 上面一段简易的ajax请求 分析**
- ajax进入Event Table,注册回调函数success。
- 执行console.log('代码执行结束')。
- ajax事件完成,回调函数success进入Event Queue。
- 主线程从Event Queue读取回调函数success并执行。
我们还经常遇到setTimeout(fn,0)这样的代码,0秒后执行又是什么意思呢?是不是可以立即执行呢?
答案是不会的,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。举例说明:
console.log("先执行这里");
setTimeout(() =>{
console.log('执行setTimeout');
},0);
console.log('执行结束');
//先执行这里
//执行结束
//执行setTimeout
上面说完了setTimeout,当然不能错过它的孪生兄弟setInterval。他俩差不多,只不过后者是循环的执行。对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。
唯一需要注意的一点是,对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。这句话请读者仔细品味。
let t = +new Date();
setInterval(()=>{
while((+new Date() -t)<3000){
//此处模拟睡眠3秒钟
}
console.log('11111');
},1000)
// 控制台会3秒后 立刻输出2个11111 后续会恢复正常 1秒打印一次11111
除了广义的同步任务和异步任务,我们对任务有更精细的定义:
macro-task(宏任务):
可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
包括整体代码script,setTimeout,setInterval ,setImmediate(Node环境支持)
micro-task(微任务):
可以理解是在当前task执行结束后立即执行的任务
包括 Promise.then,promise.catch ,promise.finally, process.nextTick(Node环境支持)
宏任务与微任务关系图
不同类型的任务会进入对应的Event Queue,比如setTimeout和setInterval会进入相同的Event Queue。
事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务
setTimeout(()=>{
console.log('setTimeout');
},1000);
new Promise(resolve =>{
console.log("promise");
resolve();
}).then(function(){
console.log('then');
});
console.log('console');
// promise
//console
//then
//setTimeout
- 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。
- 接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue。
- 遇到console.log(),立即执行。
- 整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行。
- 第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event
Queue中setTimeout对应的回调函数,立即执行。 结束
console.log(1);
setTimeout(()=>{
console.log(2);
new Promise(resolve =>{
console.log(3);
resolve();
}).then(()=>{
console.log(4);
});
},0);
new Promise(resolve =>{
console.log(5);
resolve();
}).then(()=>{
console.log(6);
});
setTimeout(()=>{
console.log(7);
new Promise(resolve=>{
console.log(8);
resolve();
}).then(()=>{
console.log(9);
})
},0);
console.log(10);
// 1 5 10 6 2 3 4 7 8 9
第一轮事件循环
1.整体script作为第一个宏任务进入主线程,遇到console.log(1);
2.遇到setTimeout, 将其回调函数分发到宏任务Event Queue中,暂且记为timer1
3.遇到promise ,new Promise直接执行,输出5。 遇到then 被分发到微任务Event Queue中,记为then1
4.遇到setTimeout, 将其回调函数分发到宏任务Event Queue中,暂且记为timer2
5.遇到console.log(10) 输出10
关系如图所示:
宏任务 | 微任务 |
---|---|
timer1 | then1 |
timer2 |
到这里 第一轮宏任务执行完成 ,看下微任务列表中then1 一个微任务,执行 输出6 。第一轮事件执行完成依次输出 : 1 5 10 6
第二次事件循环
从宏任务列表中timer1 开始:
宏任务 | 微任务 |
---|---|
then2 |
到这里 第二轮事件循环执行完成,输出: 2 3 4
第三次事件循环
从宏任务列表中timer2 开始:
宏任务 | 微任务 |
---|---|
then3 |
到这里 第三轮事件循环执行完成,输出: 7 8 9
最后整体输出为: 1 5 10 6 2 3 4 7 8 9 (此结果只是浏览器环境输出结果,Node环境输出略有不同,与执行方式有关)
参照阮一峰大神 [JavaScript 运行机制详解:再谈Event Loop](http://www.ruanyifeng.com/blog/2014/10/event-loop.html)