這篇文章的真正目的是以一種簡單的方式介紹 JavaScript 在底層如何工作,這樣即使是新程式設計師也能夠掌握這個概念並可視化編寫 JavaScript 時會發生什麼。
首先,我想專注於至少 3 個問題,這將有助於克服困難並內化背後的邏輯?
這些問題也是 Web 開發人員在面試時可能會被問到的問題,其中 JavaScript 意味著:
1。 JavaScript 是如何運作的?
2.解釋一下同步和非同步的差別?
3.或解釋這句話:JavaScript是單執行緒語言,可以是非阻塞的?
確實,編寫程式並不需要知道JavaScript 內部是如何運作的,但是為了理解背後發生的事情並感受你正在編寫的內容,學習JavaScript 是必要且至關重要的,因此對於許多擁有多年經驗的開發人員來說營運商不想知道這一點。
讓我們先知道什麼是程式? 程式只是一組指令,告訴電腦要做什麼以及如何執行任務。程式必須分配記憶體,否則,我們將無法在電腦上擁有變量,甚至無法儲存檔案。程式還應該解析(讀取)並執行專用任務,並且所有操作都發生在記憶體中。
現在,JavaScript 擁有每個瀏覽器都實作的名為 JavaScript 引擎 的引擎。例如,在 Chrome 中,它稱為 V8,在 Mozilla Firefox 中:Spider Monkey,Safari 瀏覽器:JavaScript Core Webkit。
下圖為 google chrome 的 V8 引擎
JavaScript 引擎內部發生了什麼事?
JavaScript 引擎(例如 Chrome 中的 V8)讀取我們編寫的 JavaScript 程式碼,並將其轉換為瀏覽器的機器執行指令。上圖顯示了 JavaScript 引擎的各個部分,它由兩部分組成,即內存堆**和**調用堆疊。
還要注意的是,記憶體分配發生在記憶體堆中,而解析(讀取)和執行發生在呼叫堆疊中。除此之外,記憶體堆告訴你你在程式中的位置。
讓我們用 JS (JavaScript) 程式碼看看記憶體堆中的記憶體分配
const a = 4; // now we allocated a memory. JS engine is going to remember // that a has a value of 4. const Obj = {a, b, c }; // In memory, variable 'Obj' holds the object {a, b,c} // The same as on array. the engine will remember values of the array const Array = [1,2,3,4,5]
那麼,上述程式碼在全域宣告後會出現什麼問題呢?
有一種叫做記憶體洩漏的東西。如上所述,變數聲明發生在記憶體堆中,並且它的分配大小是有限的。當您繼續聲明非常大的陣列而不是數字甚至未使用的全域變數時,這會填滿記憶體並導致記憶體洩漏。你會聽到全域變數很糟糕,因為當我們忘記清理時,我們會填滿這個記憶體堆,最終瀏覽器將無法運作。
呼叫堆疊怎麼樣?
如果我們還記得的話,讀取和執行腳本的是呼叫堆疊。我們用程式碼來說明一下吧。
const a = 4; // now we allocated a memory. JS engine is going to remember // that a has a value of 4. const Obj = {a, b, c }; // In memory, variable 'Obj' holds the object {a, b,c} // The same as on array. the engine will remember values of the array const Array = [1,2,3,4,5]
透過上面的程式碼,呼叫sack讀取第一行console.log(“x”);並被放入呼叫堆疊中,JavaScript引擎辨識出console. log已被添加,然後將其彈出到調用堆疊中,運行它,並輸出x。之後,它會刪除第一個console.log,因為它已完成運行,並將其放入第二個console.log(“y”),將其添加到呼叫堆疊中,執行y 並刪除第二個console.log。最後使用相同的過程取得console.log(“z”)。
這是最簡單的演示,如果再複雜一點怎麼辦?舉個典型的例子:
// Example Call Stak console.log("x"); console.log("y"); console.log("z"); // Result in browser // x // y // z現在,根據呼叫堆疊,上面的程式碼發生了什麼事?讓我們看看它將如何運行上面的程式碼區塊:
//呼叫堆疊
函數example1() 將首先運行,然後函數example2() 出現在調用堆疊的頂部並運行,在檢查是否存在後打印出數字7 作為輸出其他要運行的程式碼。之後,它將開始按從 console.log(‘7’)、example2()、example1() 開始的順序從呼叫堆疊中刪除,並且呼叫堆疊現在為空。
>我們還記得這句話嗎? JavaScript 是一種非阻塞的單執行緒語言。
單執行緒意味著它只有一個呼叫堆疊。它一次只能執行一件事,需要強調的是呼叫堆疊是先進後出的,就像堆疊。
其他語言可以有許多呼叫堆疊,也就是所謂的多執行緒,擁有多個呼叫堆疊可能更有利,這樣我們就不必一直等待任務。
>但是,為什麼 JavaScript 被設計為單執行緒呢?
要回答這個問題,通常在單執行緒上執行程式碼非常容易,因為多執行緒環境中不會出現複雜的場景。你實際上有一件事情需要關心。在多執行緒中,可能會出現死鎖之類的問題。有了這個理論,我們很容易知道同步程式設計意味著什麼。
同步程式簡單來說就是:執行第一行程式碼,執行第二行程式碼,執行第三行程式碼,等等......
更明確地說,這意味著console.log(“y”) 無法運行,直到console.log(“x”) 完成並且console. log (“z”) 直到前兩個都完成後才開始,因為它是一個 呼叫堆疊。
程式設計師很可能會使用 stackoverflow.com 網站。這個名字是什麼意思?出色地。讓我們來看看:
堆疊溢位是如何發生的
上圖顯示了記憶體洩漏是如何發生的以及 JavaScript 引擎的記憶體堆如何溢出。這裡,呼叫堆疊接收許多大於其大小的輸入並溢出。
可以藉助程式碼來示範堆疊溢位:
const a = 4; // now we allocated a memory. JS engine is going to remember // that a has a value of 4. const Obj = {a, b, c }; // In memory, variable 'Obj' holds the object {a, b,c} // The same as on array. the engine will remember values of the array const Array = [1,2,3,4,5]
請注意,JavaScript 是單執行緒的,一次只執行一個語句。 現在有一個問題:如果下面程式碼區塊中的console.log(“y”)有一個需要更長時間才能完成的大任務怎麼辦?例如循環遍歷具有數千或數百萬項的數組?那裡會發生什麼事?
// Example Call Stak console.log("x"); console.log("y"); console.log("z"); // Result in browser // x // y // z
第一行將執行,並假設第二行有大量工作要執行,因此第三行將等待很長時間才能執行。在上面的範例中,這沒有多大意義,但讓我們想像一個執行繁重操作的大型網站,用戶將無法執行任何操作。網站將凍結,直到任務完成並且用戶在那裡等待。對於表演來說這是一次糟糕的體驗。
嗯,對於同步任務,如果我們有一個函數需要花費很多時間,那麼它就會阻塞隊列。所以,聽起來我們需要一些非阻塞的東西。請記住我上面提到的那句話:JavaScript 是一種可以非阻塞的單執行緒語言。
理想情況下,在 JavaScript 中我們不會等待需要時間的事情。那麼,我們該如何解決這個問題呢?
作為救援,有非同步程式設計。那麼,這是什麼?
將非同步視為一種行為。同步執行很棒,因為它是可預測的。在同步中,我們知道首先發生什麼,接下來發生什麼等等,但它可能會變慢。
當我們必須執行影像處理或透過網路發出請求(例如 API 呼叫)等操作時,我們使用的不僅僅是非同步同步任務。
讓我們來看看如何用程式碼進行非同步程式設計:
const a = 4; // now we allocated a memory. JS engine is going to remember // that a has a value of 4. const Obj = {a, b, c }; // In memory, variable 'Obj' holds the object {a, b,c} // The same as on array. the engine will remember values of the array const Array = [1,2,3,4,5]
現在,根據上面的程式碼,我們似乎跳過了第二行並執行第三行,並等待 3 秒輸出結果。這是異步發生的。
為了理解這一點以及發生了什麼,讓我們使用下圖。
JavaScript 執行環境
為了運行 JavaScript,我們需要的不只是記憶體堆和呼叫堆疊。我們需要所謂的 JavaScript Run-Time,它是瀏覽器的一部分。它包含在瀏覽器中。在引擎之上,有一些稱為Web API,回調隊列和事件循環,如圖所示。
現在我們來討論一下使用 setTimeout 函數的程式碼。
// Example Call Stak console.log("x"); console.log("y"); console.log("z"); // Result in browser // x // y // z
setTimeout 函數 是 Web API 的一部分,而不是 JavaScript 的一部分,相反,它是瀏覽器提供給我們用來進行非同步程式設計的函數。因此,讓我們提供更多詳細資訊以進行澄清。
呼叫堆疊: console.log(“x”) 進入呼叫堆疊,運行,然後我們將 console.log 傳送到瀏覽器。之後,setTimeout(() =>{console.log(“y”);},3000);進入呼叫堆疊,因為第一個任務完成,然後轉到第二個任務。
現在有件事,在閱讀程式碼時,呼叫堆疊會偵測到有一個setTimeout 函數 已被設置,它不是JavaScript 的一部分,而是Web API 的一部分(參見圖JavaScript 執行時間環境)並具有其特殊的特性。發生的情況是 setTimeout 觸發 WEB API 並且由於 Web API 收到通知,該函數將從呼叫堆疊中彈出。
現在,Web API 啟動一個三秒的計時器,知道它必須在 3 秒內完成任務。請記住這裡,因為呼叫堆疊是空的,JavaScript 引擎繼續到第 3 行,即 console.log(“z”);並執行它。這就是為什麼我們得到結果 x,z 但我們在 Web API 中設定了三秒鐘的 setTimeout。然後,三秒鐘後,當時間限制結束時,setTimeout 運行並查看其中的內容,然後就完成了。完成後,Web API 將識別出它有 setTimeout 的 callback() 函數,並將其添加到 CALLBACK QUEUE 準備運行它。
我們來到最後一部分,即**事件循環*。這個函數會一直檢查呼叫堆疊是否為空。當它為空並且JavaScript 引擎中目前沒有運行任何內容時,它將檢查回調隊列並使用console.log(“z”) 找到我們的**callback()* 函數,然後將其放入CALL STACK 並運行。完成後,將其從呼叫堆疊中彈出。現在一切都是空的並且得到結果 x z y。
結論:在這篇文章中,我們看到了很多有關幕後發生的事情的信息,以完全理解 JavaScript 邏輯以及同步和異步執行的任務。
希望這將有助於新的和高級 JavaScript 程式設計師享受在 ReactJS 或 AngularJS 等 JavaScript 相關框架中進行編碼,因為這是理解高級邏輯的基礎。
>快樂編碼
參考文獻
https://www.freecodecamp.org/news/how-javascript-works-behind-the-scenes。
https://www.simplilearn.com/tutorials/javascript-tutorial/callback-function-in-javascript#
以上是JavaScript 的底層是如何運作的?的詳細內容。更多資訊請關注PHP中文網其他相關文章!