JavaScript是如何工作的:引擎,運(yùn)行時(shí)和調(diào)用堆棧的概述!
本文是旨在深入研究JavaScript及其實(shí)際工作原理的系列文章中的***篇:我們認(rèn)為通過(guò)了解JavaScript的構(gòu)建塊以及它們是如何工作的,將能夠編寫(xiě)更好的代碼和應(yīng)用程序。我們還將分享構(gòu)建 SeStHealsStad 時(shí)使用的一些經(jīng)驗(yàn)法則,這是一個(gè)輕量級(jí)的 JavaScript 應(yīng)用程序,必須保持健壯和高性能以保持競(jìng)爭(zhēng)力。
如 GitHut 統(tǒng)計(jì) 數(shù)據(jù)所示,在GitHub中的活動(dòng)存儲(chǔ)庫(kù)和總推送方面,JavaScript處于頂部。它也不落后于其他類別。
如果項(xiàng)目越來(lái)越依賴于 JavaScript,這意味著開(kāi)發(fā)人員必須利用語(yǔ)言和生態(tài)系統(tǒng)提供的所有內(nèi)容,對(duì)內(nèi)部進(jìn)行更深入的了解,以便構(gòu)建出色的軟件。
事實(shí)證明,有很多開(kāi)發(fā)人員每天都在使用JavaScript,但卻不知道背后發(fā)生了什么。
概述
幾乎每個(gè)人都已經(jīng)聽(tīng)說(shuō)過(guò) V8 引擎,大多數(shù)人都知道 JavaScript 是單線程的,或者它使用的是回調(diào)隊(duì)列。
在本文中,我們將詳細(xì)介紹這些概念,并解釋 JavaScrip 實(shí)際如何運(yùn)行。通過(guò)了解這些細(xì)節(jié),你將能夠適當(dāng)?shù)乩盟峁┑?API 來(lái)編寫(xiě)更好的、非阻塞的應(yīng)用程序。
如果您對(duì)JavaScript還比較陌生,那么本文將幫助您理解為什么JavaScript與其他語(yǔ)言相比如此“怪異”。
如果你是一個(gè)有經(jīng)驗(yàn)的JavaScript開(kāi)發(fā)人員,希望它能讓您對(duì)每天使用的JavaScript運(yùn)行時(shí)的實(shí)際工作方式有一些新的見(jiàn)解。
JavaScript引擎
JavaScript引擎的一個(gè)流行示例是Google的V8引擎。例如,在Chrome和Node.js中使用V8引擎,下面是一個(gè)非常簡(jiǎn)化的視圖:
V8引擎由兩個(gè)主要部件組成:
- emory Heap(內(nèi)存堆) — 內(nèi)存分配地址的地方
- Call Stack(調(diào)用堆棧) — 代碼執(zhí)行的地方
Runtime(運(yùn)行時(shí))
有些瀏覽器的 API 經(jīng)常被使用到(比如說(shuō):setTimeout),但是,這些 API 卻不是引擎提供的。那么,他們是從哪兒來(lái)的呢?事實(shí)上這里面實(shí)際情況有點(diǎn)復(fù)雜。
所以說(shuō)我們還有很多引擎之外的 API,我們把這些稱為瀏覽器提供 API 稱為 Web API,比如說(shuō) DOM、AJAX、setTimeout等等。
然后我們還擁有如此流行的事件循環(huán)和回調(diào)隊(duì)列。
調(diào)用棧
JavaScript是一種單線程編程語(yǔ)言,這意味著它只有一個(gè)調(diào)用堆棧。因此,它一次只能做一件事。
調(diào)用棧是一種數(shù)據(jù)結(jié)構(gòu),它記錄了我們?cè)诔绦蛑械奈恢?。如果我們運(yùn)行到一個(gè)函數(shù),它就會(huì)將其放置到棧頂,當(dāng)從這個(gè)函數(shù)返回的時(shí)候,就會(huì)將這個(gè)函數(shù)從棧頂彈出,這就是調(diào)用棧做的事情。
來(lái)個(gè)栗子:
當(dāng)程序開(kāi)始執(zhí)行的時(shí)候,調(diào)用棧是空的,然后,步驟如下:
每一個(gè)進(jìn)入調(diào)用棧的都稱為調(diào)用幀。
這能清楚的知道當(dāng)異常發(fā)生的時(shí)候堆棧追蹤是怎么被構(gòu)造的,堆棧的狀態(tài)是如何的,讓我們看一下下面的代碼:
如果這發(fā)生在 Chrome 里(假設(shè)這段代碼實(shí)在一個(gè)名為 foo.js 的文件中),那么將會(huì)生成以下的堆棧追蹤:
"堆棧溢出",當(dāng)你達(dá)到調(diào)用棧***的大小的時(shí)候就會(huì)發(fā)生這種情況,而且這相當(dāng)容易發(fā)生,特別是在你寫(xiě)遞歸的時(shí)候卻沒(méi)有全方位的測(cè)試它。我們來(lái)看看下面的代碼:
當(dāng)引擎開(kāi)始執(zhí)行這段代碼時(shí),它首先調(diào)用函數(shù)“foo”。然而,這個(gè)函數(shù)是遞歸的,并且在沒(méi)有任何終止條件的情況下開(kāi)始調(diào)用自己。因此,在執(zhí)行的每一步中,相同的函數(shù)都會(huì)被一次又一次地添加到調(diào)用堆棧中,如下所示:
然而,在某些時(shí)候,調(diào)用堆棧中的函數(shù)調(diào)用數(shù)量超過(guò)了調(diào)用堆棧的實(shí)際大小,瀏覽器決定采取行動(dòng),拋出一個(gè)錯(cuò)誤,它可能是這樣的:
在單個(gè)線程上運(yùn)行代碼很容易,因?yàn)槟悴槐靥幚碓诙嗑€程環(huán)境中出現(xiàn)的復(fù)雜場(chǎng)景——例如死鎖。
但是在一個(gè)線程上運(yùn)行也非常有限制,由于 JavaScript 只有一個(gè)調(diào)用堆棧,當(dāng)某段代碼運(yùn)行變慢時(shí)會(huì)發(fā)生什么?
并發(fā)與事件循環(huán)
當(dāng)調(diào)用堆棧中的函數(shù)調(diào)用需要花費(fèi)大量時(shí)間來(lái)處理時(shí)會(huì)發(fā)生什么情況? 例如,假設(shè)你希望在瀏覽器中使用JavaScript進(jìn)行一些復(fù)雜的圖像轉(zhuǎn)換。
你可能會(huì)問(wèn)-為什么這是一個(gè)問(wèn)題?問(wèn)題是,當(dāng)調(diào)用堆棧有函數(shù)要執(zhí)行時(shí),瀏覽器實(shí)際上不能做任何其他事情——它被阻塞了,這意味著瀏覽器不能呈現(xiàn),它不能運(yùn)行任何其他代碼,它只是卡住了,如果你想在應(yīng)用中使用流暢的頁(yè)面效果,這就會(huì)產(chǎn)生問(wèn)題。
而且這不是唯一的問(wèn)題,一旦你的瀏覽器開(kāi)始處理調(diào)用棧中的眾多任務(wù),它可能會(huì)停止響應(yīng)相當(dāng)長(zhǎng)一段時(shí)間。大多數(shù)瀏覽器都會(huì)這么做,報(bào)一個(gè)錯(cuò)誤,詢問(wèn)你是否想終止 web 頁(yè)面。
這并不是***的用戶體驗(yàn),不是嗎?
那么,我們?cè)鯓硬拍茉诓蛔枞鸘I和不使瀏覽器失去響應(yīng)的情況下執(zhí)行大量代碼呢?解決方案是異步回調(diào)。
這個(gè)在下一篇說(shuō)明,我盡快把原作者的內(nèi)容整理好!
與此同時(shí),如果你理解JavaScript應(yīng)用程序中的問(wèn)題方面遇到困難,請(qǐng)查看SessionStack。SessionStack 記錄 web 應(yīng)用程序中的所有內(nèi)容:所有 DOM 更改、用戶交互、JavaScrip t異常、堆棧跟蹤、失敗的網(wǎng)絡(luò)請(qǐng)求和調(diào)試消息。
使用SessionStack,您可以在web應(yīng)用程序中以視頻的形式重播問(wèn)題,并查看發(fā)生在用戶身上的所有事情。