相關推薦:《javascript影片教學》
#大多時候,我們在不了解記憶體管理的知識下也只開發,因為JS 引擎會為我們處理這個問題。不過,有時候我們會遇到記憶體洩漏之類的問題,而這個只有知道記憶體分配是怎麼運作的,我們才能解決這些問題。
在本文中,主要介紹記憶體分配和垃圾回收的工作原理以及如何避免一些常見的記憶體洩漏問題。
快取( Memory)生命週期
在 JS 中,當我們建立變數、函數或任何物件時,J S引擎會為此分配內存,並在不再需要時釋放它。
分配記憶體是在記憶體中保留空間的過程,而釋放記憶體則釋放空間,準備用於其他目的。
每次我們分配一個變數或建立一個函數時,該變數的儲存都會經歷以下相同的階段:
分配記憶體
- JS 會為我們處理這個問題:它分配我們創建物件所需的記憶體。
使用記憶體
- 使用記憶體是我們在程式碼中明確地做的事情:對記憶體的讀寫其實就是對變數的讀寫。
釋放記憶體
- 此步驟也由 JS 引擎處理,釋放分配的記憶體後,就可以用於新用途。
記憶體管理上下文中的「對象」不僅包括JS對象,還包括函數和函數作用域。
記憶體堆疊和堆疊
現在我們知道,對於我們在 JS 中定義的所有內容,引擎都會分配記憶體並在不再需要記憶體時將其釋放。
我想到的下一個問題是:這些東西將儲存在哪裡?
JS 引擎在兩個地方可以儲存資料:記憶體堆疊和堆疊。堆和堆疊是引擎是用於不同目的的兩個資料結構。
堆疊:靜態記憶體分配
堆疊是 JS 用來儲存靜態資料的資料結構。靜態資料是引擎在編譯時能知道大小的資料。在JS 中,包含指向物件和函數的原始值(strings
,number
,boolean
,undefined
和null
)和引用類型。
由於引擎知道大小不會改變,因此它將為每個值分配固定數量的記憶體。
在執行之前立即分配記憶體的過程稱為靜態記憶體分配。這些值和整個堆疊的限制取決於瀏覽器。
堆:動態記憶體分配
堆是另一個儲存資料的空間,JS 在其中儲存物件和函數。
與堆疊不同,JS 引擎不會為這些物件分配固定數量的內存,而根據需要分配空間。這種分配記憶體的方式也稱為動態記憶體分配。
下面將對這兩個儲存的特性進行比較:
「堆疊」 | 堆疊 |
---|---|
|
|
事例
來幾個事例,加強一下映像。
const person = { name: 'John', age: 24, };
JS 在堆中為這個物件分配記憶體。實際值仍然是原始值,這就是它們儲存在堆疊中的原因。
const hobbies = ['hiking', 'reading'];
陣列也是對象,這就是為什麼它們儲存在堆中的原因。
let name = 'John'; // 为字符串分配内存 const age = 24; // 为字分配内存 name = 'John Doe'; // 为新字符串分配内存 const firstName = name.slice(0,4); // 为新字符串分配内存
始值是不可變的,所以 JS 不會改變原始值,而是建立一個新值。
JavaScript 中的參考
所有變數首先指向堆疊。如果是非原始值,則堆疊
包含對堆疊
中物件的參考。
堆的記憶體沒有以特定的方式排序,所以我們需要在堆疊中保留對其的參考。我們可以將引用
視為地址,並將堆中的物件視為這些地址所屬的房屋。
請記住,JS 將物件和函數儲存在堆中。基本類型和參考存儲在堆疊中。
這張照片中,我們可以觀察到如何儲存不同的值。注意person
和newPerson
都如何指向同一物件。
事例
const person = { name: 'John', age: 24, };
這將在堆中建立一個新對象,並在堆疊中建立對該對象的參考。
垃圾回收
現在,我們知道 JS 如何為各種物件分配內存,但是在記憶體生命週期,還有最後一步:釋放記憶體。
就像記憶體分配一樣,JavaScript引擎也為我們處理這一步驟。更具體地說,垃圾收集器負責此工作。
一旦 JS 引擎識別變數或函數不在被需要時,它就會釋放它所佔用的記憶體。
這樣做的主要問題是,是否仍然需要一些記憶體是一個無法確定的問題,這意味著不可能有一種演算法能夠在不再需要那一刻立即收集不再需要的所有記憶體。
一些演算法可以很好地解決這個問題。我將在本節中討論最常用的方法:引用計數
和標記清除
演算法。
引用計數
當宣告了一個變數並將一個引用型別值賦值該變數時,則這個值的參考次數就是1
。如果同一個值又被賦給另一個變量,則該值得引用次數加1
。相反,如果包含這個值引用的變數又取 得了另外一個值,則這個值的引用次數減 1
。
當這個值的引用次數變成 0
時,則表示沒有辦法再存取這個值了,因而就可以將其佔用的記憶體空間回收回來。這樣,當垃圾收集器下次再運作時,它就會釋放那 些引用次數為零的值所佔用的記憶體。
我們看下面的例子。
請注意,在最後一幀中,只有hobbies
留在堆中的,因為最後引用的是物件。
週期數
引用計數
演算法的問題在於它不考慮循環引用。當一個或多個物件互相引用但無法再透過程式碼存取它們時,就會發生這種情況。
let son = { name: 'John', }; let dad = { name: 'Johnson', } son.dad = dad; dad.son = son; son = null; dad = null;
由於父物件相互引用,因此演算法不會釋放分配的內存,我們再也無法存取這兩個物件。
它們設定為null
不會使引用計數演算法識別它們不再被使用,因為它們都有傳入的參考。
標記清除
標記清除演算法對循環依賴性有解決方案。它檢測到是否可以從root
物件存取它們,而不是簡單地計算對給定物件的參考。
瀏覽器的root
是window
對象,而NodeJS中的root
是global
。
此演算法將無法存取的物件標記為垃圾,然後對其進行掃描(收集)。根物件將永遠不會被收集。
這樣,循環依賴關係就不再是問題了。在前面的範例中,dad
物件和son
物件都不能從根存取。因此,它們都將被標記為垃圾並被收集。
自2012年以來,演算法已在所有現代瀏覽器中實現。僅對效能和實作進行了改進,演算法的核心思想還是一樣的。
折衷
自動垃圾收集使我們可以專注於建立應用程序,而不用浪費時間進行記憶體管理。但是,我們需要權衡取捨。
記憶體使用
由於演算法無法確切知道何時不再需要內存,JS 應用程式可能會使用比實際需要更多的記憶體。
即使將物件標記為垃圾,也要由垃圾收集器來決定何時以及是否將收集分配的記憶體。
如果你希望应用程序尽可能提高内存效率,那么最好使用低级语言。 但是请记住,这需要权衡取舍。
性能
收集垃圾的算法通常会定期运行以清理未使用的对象。
问题是我们开发人员不知道何时会回收。 收集大量垃圾或频繁收集垃圾可能会影响性能。然而,用户或开发人员通常不会注意到这种影响。
内存泄漏
在全局变量中存储数据,最常见内存问题可能是内存泄漏。
在浏览器的 JS 中,如果省略var
,const
或let
,则变量会被加到window
对象中。
users = getUsers();
在严格模式下可以避免这种情况。
除了意外地将变量添加到根目录之外,在许多情况下,我们需要这样来使用全局变量,但是一旦不需要时,要记得手动的把它释放了。
释放它很简单,把 null
给它就行了。
window.users = null;
被遗忘的计时器和回调
忘记计时器和回调可以使我们的应用程序的内存使用量增加。 特别是在单页应用程序(SPA)中,在动态添加事件侦听器和回调时必须小心。
被遗忘的计时器
const object = {}; const intervalId = setInterval(function() { // 这里使用的所有东西都无法收集直到清除`setInterval` doSomething(object); }, 2000);
上面的代码每2秒运行一次该函数。 如果我们的项目中有这样的代码,很有可能不需要一直运行它。
只要setInterval
没有被取消,则其中的引用对象就不会被垃圾回收。
确保在不再需要时清除它。
clearInterval(intervalId);
被遗忘的回调
假设我们向按钮添加了onclick
侦听器,之后该按钮将被删除。旧的浏览器无法收集侦听器,但是如今,这不再是问题。
不过,当我们不再需要事件侦听器时,删除它们仍然是一个好的做法。
const element = document.getElementById('button'); const onClick = () => alert('hi'); element.addEventListener('click', onClick); element.removeEventListener('click', onClick); element.parentNode.removeChild(element);
脱离DOM引用
内存泄漏与前面的内存泄漏类似:它发生在用 JS 存储DOM
元素时。
const elements = []; const element = document.getElementById('button'); elements.push(element); function removeAllElements() { elements.forEach((item) => { document.body.removeChild(document.getElementById(item.id)) }); }
删除这些元素时,我们还需要确保也从数组中删除该元素。否则,将无法收集这些DOM元素。
const elements = []; const element = document.getElementById('button'); elements.push(element); function removeAllElements() { elements.forEach((item, index) => { document.body.removeChild(document.getElementById(item.id)); elements.splice(index, 1); }); }
由于每个DOM元素也保留对其父节点的引用,因此可以防止垃圾收集器收集元素的父元素和子元素。
总结
在本文中,我们总结了 JS 中内存管理的核心概念。写这篇文章可以帮助我们理清一些我们不完全理解的概念。
希望这篇对你有所帮助,我们下期再见,记得三连哦!
原文地址:https://felixgerschau.com/javascript-memory-management/
作者:Ahmad shaded
译文地址:https://segmentfault.com/a/1190000037651993
更多编程相关知识,请访问:编程入门!!
以上是詳解JavaScript中的記憶體管理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

Dreamweaver CS6
視覺化網頁開發工具

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境