JavaScript是一個單線程的編程語言涧偷,這意味著它在同一個事件內(nèi)只能做一件事
- 好處:你不必考慮并發(fā)固阁、多線程的問題物邑,這會讓代碼更加簡潔
- 壞處:你在進(jìn)行一些耗時長的操作(讀取文件庶艾、網(wǎng)絡(luò)通訊等)時掸屡,會阻塞整個程序
為了不引入多線程的概念,同時又能提高程序的效率粉洼,JS引入了異步的概念节预,具體如下
- callbacks
- promises
- async/await
雖然JS是單線程的,但是他的宿主環(huán)境(nodej属韧、瀏覽器等)是多線程的安拟。異步操作基本原理,就是讓宿主環(huán)境開啟一些副線程宵喂,來執(zhí)行異步操作糠赦,等到這個異步操作完成后,由主線程來執(zhí)行回調(diào)函數(shù)锅棕。
JS的同步操作是如何工作的拙泽?
const second = () => {
console.log('Hello there!');
}
const first = () => {
console.log('Hi there!');
second();
console.log('The End');
}
first()
我們來從JS引擎的角度來看看上述代碼是如何工作的。
執(zhí)行上下文(Execution Context)
執(zhí)行上下文是一個抽象的概念裸燎,它表示JS代碼會在哪里被執(zhí)行
- 一個函數(shù)中的代碼會在這個函數(shù)執(zhí)行上下文中被執(zhí)行
- 全局環(huán)境下的代碼會在全局執(zhí)行上下文中被執(zhí)行
- 每一個函數(shù)都有自己的執(zhí)行上下文
調(diào)用棧(call stack)
我們知道棧是一種先進(jìn)后出的結(jié)構(gòu)顾瞻。JS代碼在執(zhí)行中,會把所有的函數(shù)調(diào)用依次壓如棧中顺少。
下圖表示上述代碼執(zhí)行過程中出棧入棧的情況朋其。
我們看看到底發(fā)生了什么
- 首先王浴,全局上下文(用
main()
表示)被壓入棧中 - 當(dāng)執(zhí)行
first()
時,這個執(zhí)行上下文被壓入棧 -
console.log('Hi there!')
被壓入棧 - 當(dāng)
console.log('Hi there!')
執(zhí)行完這回梅猿,它被彈出棧
氓辣。。袱蚓。
钞啸。。喇潘。
上述就是同步代碼的執(zhí)行過程体斩,接下來我們進(jìn)入主題
JS異步操作是如何工作的?
考慮這樣的代碼
const processImage = (image) => {
/**
* doing some operations on image
**/
console.log('Image processed');
}
const networkRequest = (url) => {
/**
* requesting network resource
**/
return someData;
}
const greeting = () => {
console.log('Hello World');
}
processImage(logo.jpg);
networkRequest('www.somerandomurl.com');
greeting();
假設(shè)processImge()
是通過網(wǎng)絡(luò)請求一個圖片颖低,我們知道網(wǎng)絡(luò)讀取的速度遠(yuǎn)遠(yuǎn)慢于CPU的運(yùn)行速度絮吵。那么我們在執(zhí)行 processImge()
時,CPU就會閑置忱屑,程序就會阻塞在這里蹬敲。
JS的異步操作的核心思想就是把諸如文件讀取、網(wǎng)絡(luò)IO的操作莺戒,交給副線程(對于JS程序員不可見)伴嗡,來避免主線程被堵塞。
參考這段代碼
const networkRequest = () => {
setTimeout(() => {
console.log('Async Code');
}, 2000);
};
console.log('Hello World');
networkRequest();
console.log('Hi')
它的執(zhí)行過程是:
-
執(zhí)行所有同步代碼
- 執(zhí)行console.log('Hello World')
- 執(zhí)行networkRequest()
- 執(zhí)行setTimeOut() -- 執(zhí)行到這一步后从铲,宿主環(huán)境開啟一個副線程瘪校,來執(zhí)行這個異步操作
- 執(zhí)行console.log('Hi')
執(zhí)行完同步代碼后,JS主線程開始不斷的檢查事件循環(huán)隊列
隊列名段,看看有沒有異步操作完成2000毫秒后阱扬,setTimeOut() 完成,它的回調(diào)函數(shù)被推入事件循環(huán)隊列
此時主線程依舊在循環(huán)檢查事件循環(huán)隊列吉嫩,突然發(fā)現(xiàn)有了新的回調(diào)函數(shù)
主線程將這個回調(diào)函數(shù)壓入調(diào)用棧价认,開始執(zhí)行
調(diào)用棧的代碼執(zhí)行完之后嗅定,JS再一次開始循環(huán)檢查事件循環(huán)隊列
自娩。。渠退。忙迁。。