说到异步实现,就肯定要说同步,那么首先就来了解一下:
同步&异步
同步:从上往下逐步执行代码,只有上一步完成了,得到结果了,才会进入下一步,也就是上一步对下一步造成了堵塞。
异步:执行之后先不等待结果,直接进行下一步操作
光看文字可能难以理解,举个例子
假如你想做两道菜,一道是爆炒猪肝,从买回原料开始,猪肝先是切成片,很小的片,然后放到一只碗里,放上一些盐,放上生粉,生粉让猪肝鲜嫩。。。。。
最后为了做这道菜你耗费了大量精力,全程都很累
所以,这个事堵塞了你干别的事,这就是 同步
做菜就这么难吗?其实也不是,还有一道菜:
速食梅菜扣肉,买回来之后放入微波炉,后面就不用管了,期间你完全可以做别的事而不被它影响
这个事不会影响你做别的事,这就是 异步
看看代码
同步代码(代码1)
function someTime() {
let s = Date.now();
while(true) {
if (Date.now() - s > 2000) {
console.log(2)
break;
}
}
}
console.log(1);
someTime();
console.log(3);
// 打印结果:1 ...(2秒以后)... 2 3
异步代码(代码2)
function someTime() {
setTimeout(() => {
console.log(2);
}, 2000)
}
console.log(1);
someTime();
console.log(3);
// 打印结果:1 3 ...(2秒以后)... 2
结果如上,同步代码在遇到耗时的操作后,后续的代码只会等待,而异步代码遇到耗时的操作后,后续的代码直接掠过,不会等待
JavaScript单线程
众所周知,JavaScript是单线程的语言,所谓单线程,就是指程序执行时,按照从前到后的顺序,一个个的解决问题,必须等前面的执行完,后面的才能开始执行,我们发现,这个解释与 同步 简直如出一辙。
如此看来,JavaScript似乎只能实现同步操作,与异步无缘了~,事实也是如此吗?看如下代码:
function timeOut() {
setTimeout(() => {
console.log('time Out');
}, 0)
}
function someTime() {
let s = Date.now();
while(true) {
if (Date.now() - s > 2000) {
console.log('some Time')
break;
}
}
}
console.log(1);
timeOut();
someTime();
console.log(3);
按照单线程的思想,上面代码的执行结果应该是 1...time Out......some Time..... 3
然而事实却是 1...some Time...3...time Out
浏览器的多线程
JavaScript作为一种脚本语言,需要一个运行的环境,这个环境就是浏览器(或者node),虽然js是单线程的,但是浏览器却是多线程的。
JS线程被成为主线程,负责解析js脚本,其他的线程都在辅助他,通过其他线程,js就可以实现异步操作了。
比如代码2中从上往下的代码解析中,遇到了setTimeOut之后,js线程就会把他添加到定时触发器线程中,继续执行下面的代码,完成了异步。
更为重要的是 任务队列 的出现
任务队列
定时触发器的定时结束后,会将回调函数加入任务队列的队尾,当一个事件(包括定时结束事件)被触发时,也会把回调函数加入任务队列的队尾,等待主线程的处理。
任务添加之后不是直接处理,而是需要等待,等待主线程按顺序进行处理
宏任务&微任务
异步任务还可以细分,分为宏任务(Macrotask 或 tasks)和微任务(Mincrotask 或 jobs) ,结构如下图
首先要知道的是,微任务是要先于宏任务执行的。来段代码
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
执行结果是 1...4...3...2 ,跟你想的一样吗
可以看到setTimeOut虽然在promise的代码之上,但是却是最后一个执行的,这就是宏任务跟微任务的区别了。至于为什么 ,这个就涉及到浏览器的Event Loop问题了。
浏览器的Event Loop
总结一下浏览器执行js代码的过程:
1.执行全局Script同步代码,形成一个执行栈
;
2.在执行代码时当遇到如上 异步任务
时便会按上文所描述的将宏任务
回调加入宏任务队列
,微任务
回调加入微任务队列
;
3.然而,回调函数
放入任务队列
后也不是立即执行;会等待执行栈
中的同步任务全部执行完清空了栈后引擎才能会去任务队列
检查是否有任务,如果有那便会将这些任务加入执行栈
,然后执行!
4.执行栈
清空后,会先去检查微任务队列
是否有任务,逐一将其任务加入执行栈
中执行,期间如果又产生了微任务
那继续将其加入到微任务队列末尾,并在本周期内执行完,直到微任务队列
的任务全部 清空,执行栈
也清空后,再去检查宏任务队列
是否有任务,取到队列队头的任务放入到执行栈
中执行,其他可能又会产生微任务
,那当本次执行栈
中的任务结果清空后又会去检查微任务队列
...
5.引擎会循环执行如上步骤,这就是Event Loop!
上代码
console.log('start');
setTimeout(() => {
console.log('time1');
Pormise.resolve().then(() => {
console.log('promise1');
})
}, 0);
setTimeout(() => {
console.log('time2');
Pormise.resolve().then(() => {
console.log('promise2');
})
}, 0);
Pormise.resolve().then(() => {
console.log('promise3');
});
console.log('end');
本文参照大佬,大钟同学的文章编写,共同学习!