幾乎全部人都已經據說了V8引擎的概念,大多數人都知道JavaScript是單線程運行的或者說是使用回調隊列的。javascript
接下來,咱們將詳細的講述這些概念,解釋JavaScript究竟是怎樣運行的。當知道了這些細節後,你就能合理利用已有的API寫出更好的,非阻塞的應用。 若是你是JavaScript新手,這篇博客能夠幫助你理解爲何相對於其餘語言,JavaScript顯得如此奇怪。java
若是你是比較有經驗的JavaScript開發者,但願這篇博客可讓你對你天天使用的JavaScript運行時究竟是怎樣運行的有一些新的看法。web
一個流行的JavaScript引擎是谷歌的V8引擎。例如在Chrome和Node.js中使用的就是V8引擎。下圖是V8引擎一個很是簡單的預覽:編程
V8引擎由兩個主要組件所組成:瀏覽器
Memory Heap--內存分配區bash
Call Stack--代碼運行時棧session
大部分JavaScript開發者都使用過瀏覽器的API(例如「setTimeout」)。然而這些API都不是由引擎提供的。 那麼,它們來自哪裏呢? 真實狀況有點複雜。數據結構
因此除了引擎還有喝不少其餘的東西。有瀏覽器提供的Web API,像DOM,AJAX,setTimeout等等。 而後還有很是有名的event loop和call queue。多線程
JavaScript是一門單線程的編程語言,也就是說它只有一個調用棧,所以它只能一次作一件事。異步
調用棧是一個記錄程序運行到哪裏的數據結構。調用函數的時候,咱們會把它放到棧的最頂部。從函數返回的時候,咱們會把它從棧的最頂部彈出來。這就是調用棧作的全部的事情。
咱們來看一個例子,看一下以下代碼:
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
複製代碼
當引擎開始執行這段代碼的時候,調用棧是空的。接下來,每一步以下所示:
每次進入調用棧成爲棧楨。 這就是當一個異常拋出時,棧的記錄是怎樣組成的,基本上就是當一個異常發生的時候調用棧的狀態。看一下以下代碼:
function foo() {
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
foo();
}
function start() {
bar();
}
start();
複製代碼
若是是運行在Chrome中(假定這段代碼在foo.js文件中),將會生成以下棧記錄:
「棧溢出」--這個發生在超過調用棧最大空間的時後。這很是容易發生,特別是當你使用遞歸但又沒有很是嚴格的測試你的代碼的時候。看一下以下代碼示例:
function foo() {
foo();
}
foo();
複製代碼
當引擎開始執行這段代碼的時候,首先調用「foo」函數,可是這個函數是遞歸的,開始調用本身而且沒有結束條件。因此每一步執行,相同的函數都會一遍又一遍的加入到調用棧中,看上去就像這樣:
然而在某個時間點上調用棧中的函數調用數量將會超過調用棧的實際大小,此時瀏覽器決定採起行動,拋出一個錯誤,咱們就會看到像下面這樣的提示:
在單線程上運行代碼是很是容易的,你不用處理在多線程中發生的複雜的場景--例如死鎖。
在調用棧中存在須要花費不少時間的函數調用時會發生什麼呢?例如,想象一下你須要在瀏覽器中利用JavaScript來作一些複雜的圖片轉換。
你可能會問--這有什麼好問的?問題就是調用棧在執行函數的時候,瀏覽器不能作其餘的事--瀏覽器被阻塞了。這意味着瀏覽器將不能渲染,不能運行其餘代碼,就是說被阻塞了。若是你想要一個體驗很好,運行流暢的應用,這將會是很大的問題。
並且還不止這一個問題。一旦你的瀏覽器在調用棧中處理不少任務,它將會在很長時間內得不到響應,大多數瀏覽器將會拋出一個錯誤來採起行動,詢問你是否要結束這個web頁面。
這不是最好的用戶體驗,不是嗎?
因此,咱們怎樣才能在運行很重的代碼的時候,不阻塞UI,使瀏覽器不須要等待響應呢?解決方案就是異步回調。
咱們將會在下一節詳細講述。
對V8引擎的內部機制感興趣的同窗能夠看這裏。