JavaScript中有一些您作為開發(fā)人員可能不知道的主題。了解這些主題可以幫助您編寫更好的代碼。其中包括內(nèi)存生命周期、堆、棧和調(diào)用棧。在本教程中,您將了解這些主題以及JavaScript的工作原理。
JavaScript是一種非常寬容的編程語言。它允許你以多種方式做很多事情。它也為你做了很多工作。內(nèi)存管理就是其中之一。問問你自己:多少次你不得不考慮為你的變量或函數(shù)分配內(nèi)存?
當(dāng)你不再需要那些變量或函數(shù)時,你需要考慮釋放這些內(nèi)存多少次?機會不止一次。這同樣適用于了解堆、堆棧和調(diào)用堆棧的工作方式,或者它到底是什么。然而,您仍然可以使用JavaScript。您仍然可以編寫每天都能工作的代碼。
這些事情你不必知道。它們也不是必需的。但是,了解它們以及它們?nèi)绾喂ぷ骺梢詭椭斫釰avaScript是如何工作的。反過來,這可以幫助您編寫更好的代碼,成為更好的JavaScript。
記憶的生命周期
讓我們從最簡單的部分開始。什么是內(nèi)存生命周期,它是關(guān)于什么的,它是如何在JavaScript中工作的?內(nèi)存生命周期是指編程語言如何使用內(nèi)存。無論哪種語言,內(nèi)存的生命周期幾乎總是相同的。它由三個步驟組成。
第一步是內(nèi)存分配。當(dāng)您分配一個變量或創(chuàng)建一個函數(shù)或?qū)ο髸r,必須為它分配一定數(shù)量的內(nèi)存。第二步是內(nèi)存使用。當(dāng)您使用代碼中的數(shù)據(jù)進行讀或?qū)懖僮鲿r,您使用的是內(nèi)存。從變量中讀取或改變值就是從內(nèi)存中讀取和寫入內(nèi)存。
第三步是釋放記憶。當(dāng)你不再使用某個函數(shù)或?qū)ο髸r,內(nèi)存就會被釋放。一旦釋放,就可以再次使用。這就是對內(nèi)存生命周期的概括。JavaScript的優(yōu)點是它為您實現(xiàn)了這三個步驟。
JavaScript根據(jù)需要分配內(nèi)存。它使您更容易處理分配的內(nèi)存。最后,它還能起重和清理所有的臟亂。它使用垃圾收集來持續(xù)檢查內(nèi)存,并在不再使用時釋放內(nèi)存。結(jié)果呢?
作為一名JavaScript開發(fā)人員,您不必擔(dān)心為變量或函數(shù)分配內(nèi)存。在讀取之前,您也不必擔(dān)心選擇正確的內(nèi)存地址。而且,你不必擔(dān)心釋放你過去使用過的內(nèi)存。
堆棧和內(nèi)存堆
現(xiàn)在您已經(jīng)了解了內(nèi)存生命周期的各個步驟。你知道內(nèi)存的分配,使用和釋放。你可能會問,這些變量、函數(shù)和對象實際存儲在哪里?答案是:視情況而定。JavaScript不會將所有這些東西存儲在同一個地方。
相反,JavaScript使用了兩個位置。這些位置是堆棧和內(nèi)存堆。將使用這些位置中的哪一個取決于您當(dāng)前正在使用的內(nèi)容。
堆棧
堆棧是JavaScript僅用于存儲靜態(tài)數(shù)據(jù)的地方。這包括基本數(shù)據(jù)類型值。例如,數(shù)字、字符串、布爾值、未定義和null。這些靜態(tài)數(shù)據(jù)還包括引用。這些引用指向您創(chuàng)建的對象和函數(shù)。
這些數(shù)據(jù)有一個共同點。這些數(shù)據(jù)的大小是固定的,JavaScript在編譯時就知道這個大小。這也意味著JavaScript知道它應(yīng)該分配多少內(nèi)存,并分配這個數(shù)量。這種類型的內(nèi)存分配稱為“靜態(tài)內(nèi)存分配”。它發(fā)生在代碼執(zhí)行之前。
關(guān)于靜態(tài)數(shù)據(jù)和內(nèi)存,有一點很重要。這些原始值的大小是有限制的。對于堆棧本身也是如此。這也有局限性。這些限制有多高取決于特定的瀏覽器和引擎。
內(nèi)存堆
JavaScript存儲數(shù)據(jù)的第二個地方是內(nèi)存堆。這種存儲更加動態(tài)。當(dāng)涉及到內(nèi)存堆時,JavaScript并不分配固定數(shù)量的內(nèi)存。相反,它在需要的時候分配內(nèi)存。這種類型的內(nèi)存分配被稱為“動態(tài)內(nèi)存分配”。
哪些數(shù)據(jù)存儲在內(nèi)存堆中?堆棧是JavaScript存儲靜態(tài)數(shù)據(jù)的地方,而內(nèi)存堆是JavaScript存儲對象和函數(shù)的地方。記住,使用原語創(chuàng)建時,使用的是靜態(tài)數(shù)據(jù)。JavaScript將這些靜態(tài)數(shù)據(jù)存儲在堆棧中。
這些數(shù)據(jù)總是固定分配內(nèi)存。另一方面,當(dāng)您創(chuàng)建對象或函數(shù)時,JavaScript將它們存儲在內(nèi)存堆中。為這些分配的內(nèi)存不是固定的。它是根據(jù)需要動態(tài)分配的。
當(dāng)您創(chuàng)建一個變量并為它賦值時,它將存儲在堆棧中。當(dāng)你做同樣的嘗試時,會發(fā)生一些不同的事情,但是是針對一個對象。如果您聲明一個變量并為它分配一個對象,將會發(fā)生兩件事。首先,JavaScript將在堆棧中為該變量分配內(nèi)存。
當(dāng)涉及到對象本身時,JavaScript將把它存儲在內(nèi)存堆中。存在于堆棧中的那個變量將只指向內(nèi)存堆中的這個對象。該變量將是對該對象的引用。您可以將引用看作是現(xiàn)有事物的快捷方式或別名。
這些參考不是那些東西本身。它們只是那些“真實”事物的鏈接。您可以使用這些鏈接來訪問它們引用(它們鏈接到)的內(nèi)容并對其進行操作。
復(fù)制對象和原語
這也是為什么在JavaScript中創(chuàng)建對象副本并不是那么簡單的原因。試圖通過引用來創(chuàng)建存儲在變量中的對象的副本不會創(chuàng)建真正的副本。它不會復(fù)制對象本身。它只復(fù)制引用那個對象。這叫做淺拷貝。
當(dāng)您更改原始對象時,副本也將更改。這是因為仍然只有一個對象。但是,對那個對象有兩個引用(別名或鏈接)。當(dāng)您使用其中一個引用來更改對象時,另一個引用仍然指向相同的對象,即您剛剛更改的對象。
當(dāng)您嘗試復(fù)制原始值時,這將不會發(fā)生。當(dāng)您嘗試復(fù)制原始值,并且更改了原始值時,副本將保持不變。原因是:沒有參考資料。您正在創(chuàng)建真正的副本,并直接使用這些副本。
您可能已經(jīng)聽說過所謂的“調(diào)用堆棧”。這與我們之前在本教程中討論的堆棧不同。如您所知,堆棧是JavaScript用來存儲用原始值賦值的變量的地方。調(diào)用堆棧是不同的。
調(diào)用堆棧是JavaScript用來跟蹤函數(shù)的一種機制。當(dāng)您調(diào)用一個函數(shù)時,JavaScript會將該函數(shù)添加到調(diào)用堆棧中。如果該函數(shù)調(diào)用另一個函數(shù),JavaScript也會將該函數(shù)添加到調(diào)用堆棧中,位于第一個函數(shù)之上。
這個過程將對上一個函數(shù)調(diào)用的任何其他函數(shù)重復(fù)。當(dāng)一個函數(shù)完成時,JavaScript將從調(diào)用堆棧中刪除該函數(shù)。有兩件重要的事情。第一件事是堆棧中的每個新函數(shù)都將被添加到調(diào)用堆棧的頂部。
第二件事是調(diào)用堆棧從上到下執(zhí)行。添加到堆棧的最后一個函數(shù)將作為first執(zhí)行。添加到堆棧的第一個函數(shù)將最后執(zhí)行。這也被稱為后進先出原則。讓我們用一個簡單示例的代碼來說明這一點。
總結(jié):JavaScript中的內(nèi)存生命周期、堆、棧和調(diào)用棧
內(nèi)存生命周期、堆、堆棧和調(diào)用堆棧是不經(jīng)常討論的主題。沒有太多的材料可以用來更多地了解它們。我希望本教程能幫助您理解內(nèi)存生命周期、堆、堆棧和調(diào)用堆棧是什么,以及它們是如何工作的。
如果你喜歡這篇文章,請訂閱,這樣你就不會錯過任何未來的帖子。