1.概述
眾所周知,Javascript是一個單執行緒的語言。這意味著,在Javascript中,同一時間只能做一件事情。
這樣的設計有一些優點,例如簡單,避免了多執行緒中複雜的狀態同步,寫程式時不用考慮併發訪問。但同時也帶來了一些其他問題,其中比較突出的一個問題是:程式碼邏輯不直觀。由於Javascript是單執行緒的,其中只有一個執行序列。所以,在執行非同步操作(例如定時,網路請求這些不能立即完成的操作)時,Javascript執行時不可能在那裡等著操作完成。否則整個執行時都被阻塞在那裡了,導致其他所有的操作都無法進行,例如網頁渲染,使用者點選、滾動頁面等操作。這樣的使用者體驗是非常糟糕的。
正因為如此,Javascript使用回撥函式處理非同步操作結果。進行非同步操作時傳入一個回撥,操作完成之後由Javascript引擎執行這個回撥,將結果傳入。慢慢地,Javascript中充斥著大量的回撥。過多的使用回撥讓一段完整的邏輯被拆分成了很多片段,非常不利於閱讀與維護。回撥過多的問題在NodeJS中更為突出,故而出現了Promise
(見我的前一篇部落格)和async/await
。
那麼非同步操作完成時,Javascript執行時是怎樣感知到並呼叫對應的回撥函式的呢?
答案是EventLoop(事件迴圈)。
要了解EventLoop是怎樣運作的,我們首先需要了解Javascript是怎樣處理一個個任務,呼叫一個個函式的。這就是CallStack(呼叫棧)所做的事。
2.呼叫棧
相信有過其他語言程式設計經驗的讀者都聽說過CallStack的概念。Javascript中的CallStack類似。
CallStack是一個棧結構,棧的特點是LIFO(後入先出),出棧入棧只會在一端(也就是棧頂)進行。
CallStack是用來處理函式呼叫與返回的。每次呼叫一個函式,Javascript執行時會生成一個新的呼叫結構壓入CallStack。而函式呼叫結束返回時,JavaScript執行時會將棧頂的呼叫結構彈出。由於棧的LIFO特性,每次彈出的必然是最新呼叫的那個函式的結構。
Javascript啟動時,從檔案或標準輸入載入程式。載入完成時,Javascript執行時會生成一個匿名的函式,函式體就是輸入的程式碼。這個函式就有點類似於C/C++中的main()
函式,是我們的入口函式。我們姑且稱之為<main>
函式。Javascript啟動時,首先呼叫就是<main>
函式。看下面程式碼:
function func1() {
console.log('in function1');
}
function func2() {
func1();
console.log('in function2');
}
function func3() {
func2();
console.log('in function3');
}
func3();
複製程式碼
上面程式碼很好理解,我們來看看Javascript是如何執行這段程式碼的。
Javascript首先載入程式碼,建立一個匿名<main>
包裹這段程式碼並呼叫該函式。<main>
函式執行,依次定義函式func1
、func2
、func3
,然後呼叫函式func3
。為func3
建立呼叫結構並壓棧。函式func3
中呼叫func2
,為func2
建立呼叫結構並壓棧。函式func2
中呼叫func1
,為func1
建立呼叫結構並壓棧。這個過程中,CallStack的變化如下。
然後,函式func1
執行完成,從棧頂彈出呼叫結構。然後func2
繼續執行,func2
執行完成後從棧頂彈出其呼叫結構。然後func3
繼續執行,func3
執行完成後從棧頂彈出其呼叫結構。這個過程中,CallStack的變化如下。
當然,我這裡有一個地方不太嚴謹。不知道讀者有沒有注意到,console.log
也是函式函式哦。所以在func1
中呼叫console.log
時,CallStack上也會有對應的呼叫堆疊。func2
,func3
中的console.log
呼叫同樣如此。有興趣的話,可以自己畫一畫完整的呼叫流程,這樣可以加深理解?。
這裡我推薦大家使用Google Chrome的開發者工具來幫助我們理解CallStack。下圖是上面程式碼在開發者工具中的一步步執行的結果:
-
Chrome中
<main>
稱為<anonymous>
。 -
單步執行時,重點觀察右側工具欄中Call Stack一欄的變化。
在CallStack中執行的函式,我們稱之為一個task(任務)。
接下來,我們來思考這樣一個問題:setTimeout
、setInterval
、AJAX
請求這些功能是怎麼實現的?
一位牛人Philip Roberts曾經將V8引擎(Chrome內建的Javascript引擎)原始碼下載下來,然後用grep
查詢,發現原始碼中並沒有實現這些函式的程式碼?。
那麼這些函式到底是如何實現的,又是如何與Javascript引擎互動的呢?
答案是:宿主(網頁中指的是瀏覽器,Node中指的是Node引擎)提供實現,並在操作完成時將結果(非同步的,會有延遲)放入Javascript引擎的task佇列,由Javascript引擎處理。
3.事件迴圈
EventLoop顧名思義,其實就是Javascript引擎中的一個迴圈,它就是一個不停地從任務佇列(task queue)中取出任務執行的過程。
我們前面詳細瞭解了CallStack以及Javascript啟動時是如何處理的。但是<main>
退出後,Javascript引擎就沒事做了嗎?當然不是,有很多工會不定時的觸發需要Javascript引擎去處理。例如,使用者點選按鈕,定時器,頁面渲染等。
其實,Javascript引擎中維護著一個任務佇列。當CallStack中沒有任務在執行時,引擎會從任務佇列中取出任務壓入CallStack處理。我們通過程式碼來具體看看(引用jesstelford):
setTimeout(() => {
console.log('hi');
}, 1000);
複製程式碼
我們的Js程式碼,call stack,task queue和Web APIs(瀏覽器中實現)關係如下:
[code] | [call stack] | [task queue] | | [Web APIs] |
--------------------|-------------------|--------------| |---------------|
setTimeout(() => { | | | | |
console.log('hi') | | | | |
}, 1000) | | | | |
| | | | |
複製程式碼
開始時,程式碼未執行,所有都是空的。
[code] | [call stack] | [task queue] | | [Web APIs] |
--------------------|-------------------|--------------| |---------------|
setTimeout(() => { | <main> | | | |
console.log('hi') | | | | |
}, 1000) | | | | |
| | | | |
複製程式碼
開始執行程式碼,壓入我們的<main>
函式。
[code] | [call stack] | [task queue] | | [Web APIs] |
--------------------|-------------------|--------------| |---------------|
> setTimeout(() => { | <main> | | | |
console.log('hi') | setTimeout | | | |
}, 1000) | | | | |
| | | | |
複製程式碼
執行第一行程式碼,呼叫函式setTimeout
。我們前面說過,每個函式呼叫都會建立一個新的呼叫記錄壓到棧上。
[code] | [call stack] | [task queue] | | [Web APIs] |
--------------------|-------------------|--------------| |---------------|
setTimeout(() => { | <main> | | | timeout, 1000 |
console.log('hi') | | | | |
}, 1000) | | | | |
| | | | |
複製程式碼
setTimeout
執行完成,從棧中移除對應呼叫記錄。Web APIs
記錄超時和回撥,超時機制由瀏覽器實現。
[code] | [call stack] | [task queue] | | [Web APIs] |
--------------------|-------------------|--------------| |---------------|
setTimeout(() => { | | | | timeout, 1000 |
console.log('hi') | | | | |
}, 1000) | | | | |
| | | | |
複製程式碼
程式碼中沒有其他邏輯,<main>
函式結束,從棧移除呼叫資訊。
[code] | [call stack] | [task queue] | | [Web APIs] |
--------------------|-------------------|--------------| |---------------|
setTimeout(() => { | | function <-----timeout, 1000 |
console.log('hi') | | | | |
}, 1000) | | | | |
| | | | |
複製程式碼
超時時間到了,Web APIs
將回撥放入task queue中。
[code] | [call stack] | [task queue] | | [Web APIs] |
--------------------|-------------------|--------------| |---------------|
setTimeout(() => { | function <---function | | |
console.log('hi') | | | | |
}, 1000) | | | | |
| | | | |
複製程式碼
EventLoop檢測到Javascript沒有任務處理,從task queue中取出任務執行。
[code] | [call stack] | [task queue] | | [Web APIs] |
--------------------|-------------------|--------------| |---------------|
setTimeout(() => { | function | | | |
> console.log('hi') | console.log | | | |
}, 1000) | | | | |
| | | | |
複製程式碼
執行該回撥函式,回撥函式中又呼叫了console.log
函式。
[code] | [call stack] | [task queue] | | [Web APIs] |
--------------------|-------------------|--------------| |---------------|
setTimeout(() => { | function | | | |
console.log('hi') | | | | |
}, 1000) | | | | |
| | | | |
> hi
複製程式碼
console.log
執行完成,輸出"hi"。
[code] | [call stack] | [task queue] | | [Web APIs] |
--------------------|-------------------|--------------| |---------------|
setTimeout(() => { | | | | |
console.log('hi') | | | | |
}, 1000) | | | | |
| | | | |
> hi
複製程式碼
回撥函式執行完成,CallStack再次為空。
上面就是setTimeout
的執行過程,從中開始看出EventLoop在幕後做的工作。
下面我們再來看一段程式碼:
console.log("start");
setTimeout(() => {
console.log("timeout");
}, 0);
Promise.resolve()
.then(() => {
console.log("promise1");
})
.then(() => {
console.log("promise2");
});
console.log("end");
複製程式碼
這段程式的輸出是什麼?建議大家先思考一下,最好能動筆畫一畫圖?。
4.微任務佇列
接著上一節的程式碼,符合標準(很多舊版本的瀏覽器實現都是不符合標準的,具體參見參考連結)的輸出應該是:
start
end
promise1
promise2
timeout
複製程式碼
但是,為?什?麼?
實際上,Javascript中有另外一種佇列。Promise
的回撥是被放入這個佇列的。這個佇列叫做microtask queue(微任務佇列),ES6標準中叫Job Queue。microTask的優先順序是比task高的,也就是說microtask佇列中的任務要先處理。
-
EventLoop檢測到當前沒有任務在執行,首先檢查microtask佇列中有沒有需要處理的任務。如果有那麼一個個執行,直到microtask佇列為空。
-
microtask佇列中沒有任務了,執行task佇列中的任務。這裡需要注意,**每執行一個task佇列中的任務,就檢查一下microtask佇列狀態。將microtask佇列中所有任務都執行完成之後,再從task佇列中取出任務執行。
下面我們看看上面那段程式碼是怎麼一步步執行的:
(為方便起見我們稱setTimeout
回撥為timeoutcb
,稱第一個then
成功回撥為promisecb1
,第二個then
回撥為promisecb2
)
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
--------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | | | | | | |
2. | | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10.}) | | | | | | |
11..then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13.}); | | | | | | |
14. | | | | | | |
15.console.log("end"); | | | | | | |
複製程式碼
開始時,call stack、task queue、microtask queue都為空。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
> 1. console.log("start"); | <main> | | | | | |
2. | console.log | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
15. console.log("end"); | | | | | | |
複製程式碼
程式開始執行,壓入<main>
函式。首先執行第一行程式碼,console.log("start")
,將console.log
壓棧。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
> 1. console.log("start"); | <main> | | | | | |
2. | | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
15. console.log("end"); | | | | | | |
> start
複製程式碼
console.log
執行完成,輸出"start"。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | <main> | | | | | |
2. | setTimeout | | | | | |
> 3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
15. console.log("end"); | | | | | | |
> start
複製程式碼
第二行為空跳過,開始執行第三行程式碼,setTimeout
壓棧。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | <main> | timeoutcb <------------------------~~timeoutcb, 0~~|
2. | | | | | | |
> 3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
15. console.log("end"); | | | | | | |
> start
複製程式碼
setTimeout
執行完成,由於超時是0,所以立即回撥立即進入task queue中。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | <main> | timeoutcb | | promisecb1 | | |
2. | | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
> 7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
15. console.log("end"); | | | | | | |
> start
複製程式碼
程式碼執行到第7行:
-
首先
Promise.resolve
壓棧,執行完成後返回一個Promise
物件。 -
然後呼叫該物件的
then
方法,該方法壓棧,執行完成後返回一個全新的Promise
物件,我們稱該物件為promise1。由於Promise.resolve
返回物件的狀態為resolved
,所以promise1
回撥直接進入microtask佇列。 -
接著又執行新物件的
then
方法,我們稱該物件為promise2。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | <main> | timeout | | promisecb1 | | |
2. | console.log | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
> 15. console.log("end"); | | | | | | |
> start
複製程式碼
程式碼執行到第15行,console.log
壓棧。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | <main> | timeout | | promisecb1 | | |
2. | | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
> 15. console.log("end"); | | | | | | |
> start
> end
複製程式碼
console.log("end")
執行完成,輸出"end",出棧。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | | timeout | | promisecb1 | | |
2. | | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
> 15. console.log("end"); | | | | | | |
> start
> end
複製程式碼
<main>
函式沒有邏輯需要執行了,出棧。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | promisecb1 | timeout | | | | |
2. | | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
> 15. console.log("end"); | | | | | | |
> start
> end
複製程式碼
EventLoop檢查到microtask佇列中有任務需要執行,將promisecb1
取出壓入call stack。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | | timeout | | promisecb2 | | |
2. | | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
15. console.log("end"); | | | | | | |
> start
> end
> promise1
複製程式碼
promisecb1
執行完成,輸出"promise1",並且返回undefined
。(其實在這裡還有一個console.log
壓棧出棧的過程,我就不畫了,下同)
在深入理解Javascript之Promise中看到,如果返回一個值,那麼物件立刻變為resolved
。所以第二個then
的回撥需要安排執行,進入microtask佇列。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | promisecb2 | timeout | | | | |
2. | | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
15. console.log("end"); | | | | | | |
> start
> end
> promise1
複製程式碼
EventLoop檢測到call stack中沒有正在執行的任務,同時microtask佇列不為空。從microtask佇列取出任務壓入call stack。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | | timeout | | | | |
2. | | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
15. console.log("end"); | | | | | | |
> start
> end
> promise1
> promise2
複製程式碼
promisecb2
執行完成,輸出"promise2"。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | timeoutcb | | | | | |
2. | | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
15. console.log("end"); | | | | | | |
> start
> end
> promise1
> promise2
複製程式碼
接著,EventLoop檢測到call stack和microtask佇列都為空,從task佇列中取出timeoutcb
壓入棧。
[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] |
---------------------------------|-------------------|--------------| |-------------------| |---------------|
1. console.log("start"); | | | | | | |
2. | | | | | | |
3. setTimeout(() => { | | | | | | |
4. console.log("timeout"); | | | | | | |
5. }, 0); | | | | | | |
6. | | | | | | |
7. Promise.resolve() | | | | | | |
8. .then(() => { | | | | | | |
9. console.log("promise1"); | | | | | | |
10. }) | | | | | | |
11. .then(() => { | | | | | | |
12. console.log("promise2"); | | | | | | |
13. }); | | | | | | |
14. | | | | | | |
15. console.log("end"); | | | | | | |
> start
> end
> promise1
> promise2
> timeout
複製程式碼
timeoutcb
執行完成,輸出"timeout"。
5.總結
通過這篇文章,我們瞭解到Javascript時如何通過call stack來處理函式的呼叫與返回的。setTimeout
等非同步機制其實是宿主提供實現,並在非同步操作完成負責將回撥放入任務佇列,最後由EventLoop在適合的時機取出壓入call stack實際執行。
我們還看到了另外一種佇列——microtask佇列。該佇列中存放的一般是優先順序較高的任務,例如Promise
的回撥處理函式。
每當call stack中沒有正在執行的任務時,EventLoop會優先從microtask佇列中取出任務執行,當該佇列為空時才會從task佇列取。
在使用一門框架或語言時,對於是否需要了解底層運作機制和原理,往往會有比較大的爭論。有人說,我不瞭解內部原理同樣可以寫出好程式,那為什麼還需要花時間去研究呢? 對此,我覺得了解底層原理還是非常有必要的。有下面幾個好處:
-
可以讓我們看到全貌,瞭解整個系統是如何運作的。
-
底層原理大多是相通的,例如幾乎所有語言的函式呼叫底層都是利用CallStack來實現的。學會了Javascript的CallStack運作機制,在學習其他語言的相關概念時往往能事半功倍。
-
瞭解底層可以讓我們心中有數,明白什麼事情能做,什麼事情不能做。例如Javascript是單執行緒的,我們寫程式碼時一定不能讓執行緒阻塞了?。
-
瞭解底層可以讓我們更好的優化程式碼。當程式效能出現瓶頸時,可以更快地定位問題。