對JavaScript有個很模糊的印象,它是單執行緒異步的。本文主要來說說JavaScript到底是怎麼運作的。但在這之前,讓我們先理一下這些概念(現學現賣)。
行程(Process)是系統資源分配與調度的單元。一個運作著的程式就對應了一個行程。一個進程包括了運行中的程式和程式所使用到的記憶體和系統資源。如果是單核心CPU的話,在同一時間內,有且只有一個行程在運作。但是,單核心CPU也能實現多任務同時運行,例如你邊聽網易雲音樂的每日推薦歌曲,邊在網易有道雲筆記上寫博文。這算開了兩個進程(多進程),那運行的機制就是一會兒播放一下歌,一會兒響應一下你的打字,但由於CPU切換的速度很快,你根本感覺不到,以至於你認為這兩個進程是在同時運行的。進程之間是資源隔離的。
那線程(Thread)是什麼?執行緒是進程下的執行者,一個行程至少會開啟一個執行緒(主執行緒),也可以開啟多個執行緒。例如網易雲音樂一邊播放音頻,一邊顯示歌詞。多進程的運行其實也就是透過進程中的執行緒來執行的。一個行程下的執行緒是共享資源的。當多個執行緒同時操作同一個資源的時候,就會出現資源爭搶的問題。這又是另外一個問題了。
並行(Parallelism)是指程式的運作狀態,在同一個時間內有幾件事情並行在處理。由於一個執行緒在同一時間只能處理一件事情,所以並行需要多個執行緒在同一時間執行多件事情。
而並發(Concurrency)是指程式的設計結構,在同一時間內多件事情能被交替地處理。重點是,在某個時間內只有一件事情在執行。例如單核心CPU能實現多任務運行的過程就是並發的。
同步非同步是指程式的行為。同步(Synchronous)是程式發出呼叫的時候,一直等待直到回傳結果,沒有結果之前不會回傳。也就是說,同步是呼叫者主動等待呼叫過程。
非同步(Asynchronous)是發出呼叫之後,馬上回傳,但不會馬上回傳結果。呼叫者不必主動等待,當被呼叫者得到結果之後會主動通知呼叫者。
舉個例子,去奶茶店買飲料。同步就是,一個顧客說出需求(請求),然後一直等著服務生做好飲料,顧客拿到自己點的飲料之後才離開;然後下一個顧客繼續重複上述過程。非同步就是,顧客先排隊點單,點完之後拿著單子在一邊,等服務生做好之後會叫號,叫到你了你去拿就好。
所以執行緒跟同步異步沒有直接的關係,單執行緒也是可以實現異步的。至於實現的方式,以下會具體說到。
阻塞與非阻塞是指等待狀態。阻塞(Blocking)是指呼叫在等待的過程中線程被「掛起」(CPU資源被分配到其他地方去了)。
非阻塞(Non-blocking)是指等待的過程CPU資源還在該執行緒中,執行緒還能做其他的事情。
以剛才排隊買飲料的例子,阻塞就是你在等待的時候什麼事情也做不了,而非阻塞是你在等待的時候可以管自己先做其他的事情。
所以,同步可以阻塞也可以非阻塞,非同步可以阻塞也可以非阻塞。
大概理清楚上述概念之後呢,就知道單執行緒和非同步是沒有矛盾的。那JS是如何執行的呢? JS其實就是一門語言,說是單執行緒還是多執行緒得結合具體運行環境。 JS的運作通常是在瀏覽器中進行的,具體由JS引擎去解析和運行。下面我們來具體了解瀏覽器。
目前最受歡迎的瀏覽器為:Chrome,IE,Safari,FireFox,Opera。瀏覽器的核心是多執行緒的。一個瀏覽器通常由以下幾個常駐的執行緒:
渲染引擎執行緒:顧名思義,該執行緒負責頁面的渲染
通常講到瀏覽器的時候,我們會說到兩個引擎:渲染引擎和JS引擎。渲染引擎就是如何渲染頁面,Chrome/Safari/Opera用的是Webkit引擎,IE用的是Trident引擎,FireFox用的是Gecko引擎。不同的引擎對同一個樣式的實作不一致,就導致了經常被人詬病的瀏覽器樣式相容性問題。這裡我們不做具體討論。
JS引擎可以說是JS虛擬機,負責JS程式碼的解析與執行。通常包括以下幾個步驟:
詞法分析:將原始程式碼分解為有意義的分詞
var x = 10; function foo(){ var y=20; function bar(){ var z=15; } bar(); } foo();程式碼運行,先進入全域上下文。然後執行
foo()的時候,就進入了foo上下文,當然此時全域上下文還在。當執行
bar()的時候,又進入了bar上下文。執行完畢
bar(),回到foo上下文。執行完
foo(),又回到全域上下文。所以,執行過程執行上下文會形成一個呼叫堆疊(Call stack),先進後出。
// 进栈 3 bar Context => => 2 foo Context => 2 foo Context 1 global Context 1 global Context 1 global Context // 出栈 3 bar Context 2 foo Context => 2 foo Context => => 1 global Context 1 global Context 1 global Context在JS執行過程中,有且僅有一個執行上下文在運作。因為JS是單線程的,一次只能做一件事。 以上的過程都是同步執行的。 非同步執行-事件循環我們回顧JS中自帶了哪些原生的非同步事件:
function foo(){ console.log(1); } function bar(){ console.log(2); } foo(); setTimeout(function cb(){ console.log(3); }); bar();依照上一節的分析,先進入全域上下文,執行至
foo(),進入了foo上下文環境;執行
console.log(1),控制台輸出1;foo上下文環境出棧,運行至
setTimeout,交給
瀏覽器的定時處理線程;運行至bar() ,進入了bar上下文環境;執行
console.log(2),控制台輸出2;foo上下文環境出棧;等到
瀏覽器執行緒執行完setTimeout,返回
cb()回呼函數至目前
任務佇列;當發現執行堆疊為空時,瀏覽器的JS引擎會執行一次循環,將事件佇列的隊首出隊至JS執行堆疊中;執行cb(),進入cb上下文環境;執行
console.log(3),控制台輸出3;事件佇列為空,全域上下文出棧。
瀏覽器執行緒,任務佇列以及JS引擎。所以,我們可以看出,JS的非同步請求,借助了而它所在的運行環境瀏覽器來處理並且傳回結果。而且,這也解釋了為什麼那些回呼函數的this指向
window,因為這些非同步的程式碼都是在全域上下文環境下執行的。
Philip Roberts: Help, I'm stuck in an event-loop.
Philip Roberts: What the heck is the event loop anyway?
jquery中Ajax的非同步與同步
以上是詳解JavaScript運行機制以及概念分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!