JavaScript?弱引用強(qiáng)引用底層示例詳解
正文
內(nèi)存和性能管理是軟件開發(fā)的重要方面,也是每個軟件開發(fā)人員都應(yīng)該注意的方面。雖然弱引用很有用,但在 JavaScript 中并不經(jīng)常使用。在 ES6 版本中,JavaScript 引入了 WeakSet
和 WeakMap
。
1. 弱引用
與強(qiáng)引用不同,弱引用并不阻止被引用的對象被垃圾收集器回收或收集,即使它是內(nèi)存中對對象的唯一引用。
在討論強(qiáng)引用、WeakSet
、Set
、WeakMap
和 Map
之前,讓我們用下面的代碼片段來演示弱引用:
// 創(chuàng)建 WeakMap 對象的實(shí)例 let human = new WeakMap(); // 創(chuàng)建一個對象,并將其賦值給名為 man 的變量 let man = { name: "xiaan" }; // 調(diào)用 human 的 set 方法,并傳遞兩個參數(shù)(鍵和值)給它 human.set(man, "done") console.log(human)
以上代碼的輸出如下:
WeakMap {{…} => 'done'} man = null; console.log(human)
當(dāng)我們將 man
變量重新賦值為 null
時,內(nèi)存中對原始對象的唯一引用是弱引用,它來自我們前面創(chuàng)建的 WeakMap
。當(dāng) JavaScript 引擎運(yùn)行垃圾收集過程時,man
對象將從內(nèi)存和我們分配給它的 WeakMap
中刪除。這是因?yàn)樗且粋€弱引用,并且它不阻止垃圾收集。接下來我們談?wù)剰?qiáng)引用。
2. 強(qiáng)引用
JavaScript 中的強(qiáng)引用是防止對象被垃圾回收的引用。它將對象保存在內(nèi)存中。
下面的代碼片段說明了強(qiáng)引用的概念:
let man = {name: "xiaan"}; let human = [man]; man = null; console.log(human);
以上代碼的結(jié)果如下:
// 長度為 1 的對象數(shù)組 [{…}]
由于 human
數(shù)組和對象之間存在強(qiáng)引用。對象被保留在內(nèi)存中,可以通過以下代碼訪問:
console.log(human[0])
這里要注意的重要一點(diǎn)是,弱引用不會阻止對象被垃圾回收,而強(qiáng)引用卻會阻止對象被垃圾回收。
3. JavaScript 的垃圾收集
與所有編程語言一樣,在編寫 JavaScript 時,內(nèi)存管理是需要考慮的關(guān)鍵因素。與 C 語言不同,JavaScript 是一種高級編程語言,它在創(chuàng)建對象時自動分配內(nèi)存,在不再需要對象時自動清除內(nèi)存。當(dāng)不再使用對象時清除內(nèi)存的過程稱為垃圾收集。在談?wù)?JavaScript 中的垃圾收集時,幾乎不可能不觸及可達(dá)性的概念。
3.1 可達(dá)性
在特定作用域中的所有值或在作用域中使用的所有值都被稱為在該作用域中的“可達(dá)”,并被稱為“可達(dá)值”??稍L問的值總是存儲在內(nèi)存中。
在以下情況下,值被認(rèn)為是可達(dá)的:
- 程序根中的值或從根中引用的值,如全局變量或當(dāng)前執(zhí)行的函數(shù)、它的上下文和回調(diào)。
- 通過引用或引用鏈從根中訪問的值(例如,全局變量中的對象引用另一個對象,該對象也引用另一個對象——這些都被認(rèn)為是可訪問的值)。
下面的代碼片段說明了可達(dá)性的概念:
var person = {name: "xiaan"};
這里我們有一個對象,它的鍵值對(name
為 "xiaan"
)引用全局變量 person
。如果我們通過賦值 null
來覆蓋 person
的值:
person = null;
那么對象將被垃圾回收,"xiaan"
值將無法再次訪問。下面是另一個例子:
var person = {name: "xiaan"}; var programmer = person;
從上面的代碼片段中,我們可以從 person
變量和 programmer
變量訪問 object 屬性。然而,如果我們將person
設(shè)置為 null
:
person = null;
那么對象仍然在內(nèi)存中,因?yàn)樗梢酝ㄟ^ programmer
變量訪問。簡單地說,這就是垃圾收集的工作方式。
注意:默認(rèn)情況下,JavaScript 對其引用使用強(qiáng)引用。要在 JavaScript 中實(shí)現(xiàn)弱引用,可以使用 WeakMap
、WeakSet
或 WeakRef
。
4. Set VS WeakSet
set
對象是一個只有一次出現(xiàn)的唯一值的集合。像數(shù)組一樣,集合沒有鍵值對。我們可以使用for...of
和 .forEach
的數(shù)組方法遍歷。
讓我們用以下片段來說明這一點(diǎn):
let setArray = new Set(["Joseph", "Frank", "John", "Davies"]); for (let names of setArray){ console.log(names) }// Joseph Frank John Davies
我們也可以使用 .forEach
遍歷:
setArray.forEach((name, nameAgain, setArray) =>{ console.log(names); });
WeakSet
是唯一對象的集合。正如其名稱一樣,弱集使用弱引用。以下是 WeakSet()
的特性:
- 它可能只包含對象。
- 集合中的對象可以在其他地方訪問。
- 它不能循環(huán)遍歷。
- 像
Set()
一樣,WeakSet()
有add
、has
和delete
方法。
下面的代碼說明了如何使用 WeakSet()
和一些可用的方法:
const human = new WeakSet(); let person = {name: "xiaan"}; human.add(person); console.log(human.has(person)); // true person = null; console.log(human.has(person)); // false
在第 1 行,我們創(chuàng)建了 WeakSet()
的一個實(shí)例。在第 3 行,我們創(chuàng)建了對象并將它分配給變量 person
。在第 5 行,我們將 person
添加到 WeakSet()
中。在第 9 行,我們將 person
引用設(shè)為空。第 11 行代碼返回false
,因?yàn)?WeakSet()
將被自動清除,因此,WeakSet()
不會阻止垃圾回收。
5. Map VS WeakMap
我們從上面關(guān)于垃圾收集的部分了解到,只要可以訪問,JavaScript 引擎就會在內(nèi)存中保留一個值。讓我們用一些片段來說明這一點(diǎn):
let person = {name: "xiaan"}; // 對象可以從引用中訪問 // 覆蓋引用 person. person = null; // 該對象不能被訪問
當(dāng)數(shù)據(jù)結(jié)構(gòu)在內(nèi)存中時,數(shù)據(jù)結(jié)構(gòu)的屬性被認(rèn)為是可訪問的,并且它們通常保存在內(nèi)存中。如果將對象存儲在數(shù)組中,那么只要數(shù)組在內(nèi)存中,即使沒有其他引用,也仍然可以訪問對象。
let person = {name: "xiaan"}; let arr = [person]; // 覆蓋引用 person = null; console.log(array[0]) // {name: 'xiaan'}
即使引用被覆蓋,我們?nèi)匀荒軌蛟L問這個對象因?yàn)閷ο蟊槐4嬖跀?shù)組中。因此,只要數(shù)組仍然在內(nèi)存中,它就保存在內(nèi)存中。因此,它沒有被垃圾回收。由于我們在上面的例子中使用了數(shù)組,我們也可以使用 map
。當(dāng) map
仍然存在時,存儲在其中的值將不會被垃圾回收。
let map = new Map(); let person = {name: "xiaan"}; map.set(person, "person"); // 覆蓋引用 person = null; // 還能訪問對象 console.log(map.keys());
與對象一樣,map
可以保存鍵—值對,我們可以通過鍵訪問值。但是對于 map
,我們必須使用 .get()
方法來訪問值。
根據(jù) Mozilla Developer Network,Map
對象保存鍵—值對并記住鍵的原始插入順序。任何值(包括對象值和原語值)都可以用作鍵或值。
與 map
不同,WeakMap
保存弱引用。因此,如果這些值在其他地方?jīng)]有被強(qiáng)引用,它不會阻止垃圾回收刪除它引用的值。除此之外,WeakMap
與 map
是相同的。由于弱引用,WeakMap
不可枚舉。
對于 WeakMap
,鍵必須是對象,值可以是數(shù)字或字符串。
下面的代碼片段說明了 WeakMap
的工作原理和其中的方法:
// 創(chuàng)建一個 WeakMap let weakMap = new WeakMap(); let weakMap2 = new WeakMap(); // 創(chuàng)建一個對象 let ob = {}; // 使用 set 方法 weakMap.set(ob, "Done"); // 你可以將值設(shè)置為一個對象甚至一個函數(shù) weakMap.set(ob, ob) // 可以設(shè)置為undefined weakMap.set(ob, undefined); // WeakMap 也可以是值和鍵 weakMap.set(weakMap2, weakMap) // 要獲取值,使用 get 方法 weakMap.get(ob) // Done // 使用 has 方法 weakMap.has(ob) // true weakMap.delete(ob) weakMap.has(ob) // false
在沒有其他引用的 WeakMap
中使用對象作為鍵的一個主要副作用是,它們將在垃圾收集期間自動從內(nèi)存中刪除。
6. WeakMap 的應(yīng)用
WeakMap
可以用于 web 開發(fā)的兩個領(lǐng)域:緩存和額外的數(shù)據(jù)存儲。
6.1 緩存
這是一種 web 技術(shù),它涉及到保存(即存儲)給定資源的副本,并在請求時返回它。可以緩存函數(shù)的結(jié)果,以便在調(diào)用函數(shù)時重用緩存的結(jié)果。
讓我們來看看實(shí)際情況。
let cachedResult = new WeakMap(); // 存儲結(jié)果的函數(shù) function keep(obj){ if(!cachedResult.has(obj){ let result = obj; cachedResult.set(obj, result); } return cachedResult.get(obj); } let obj = {name: "xiaan"}; let resultSaved = keep(obj) obj = null; // console.log(cachedResult.size); Possible with map, not with WeakMap
如果我們在上面的代碼中使用 Map()
而不是 WeakMap()
,并且對 keep()
函數(shù)有多次調(diào)用,那么它只會在第一次調(diào)用時計(jì)算結(jié)果,并在其他時候從 cachedResult
檢索結(jié)果。副作用是,每當(dāng)對象不需要時,我們就需要清理 cachedResult
。使用 WeakMap()
,一旦對象被垃圾回收,緩存的結(jié)果就會自動從內(nèi)存中刪除。緩存是提高軟件性能的一種很好的方法——它可以節(jié)省數(shù)據(jù)庫使用、第三方 API 調(diào)用和服務(wù)器對服務(wù)器請求的成本。通過緩存,請求結(jié)果的副本被保存在本地。
6.2 額外的數(shù)據(jù)存儲
WeakMap()
的另一個重要用途是額外的數(shù)據(jù)存儲。想象一下,我們正在建立一個電子商務(wù)平臺,我們有一個計(jì)算訪客數(shù)量的程序,我們希望能夠在訪客離開時減少計(jì)數(shù)。這個任務(wù)在 Map
中要求很高,但在 WeakMap()
中很容易實(shí)現(xiàn):
let visitorCount = new WeakMap(); function countCustomer(customer){ let count = visitorCount.get(customer) || 0; visitorCount.set(customer, count + 1); } let person = {name: "xiaan"}; // 統(tǒng)計(jì)訪問人數(shù) countCustomer(person) // 訪客離開 person = null;
使用 Map()
,我們必須在客戶離開時清除 visitorCount
,否則,它將在內(nèi)存中無限增長,占用空間。但是使用 WeakMap()
,我們不需要清理 visitorCount
,一旦一個人(對象)變得不可訪問,它就會自動被垃圾回收。
7. 小結(jié)
在本文中,我們了解了弱引用、強(qiáng)引用和可達(dá)性的概念,并盡可能地將它們與內(nèi)存管理聯(lián)系起來。
更多關(guān)于JavaScript 弱引用強(qiáng)引用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript簡單進(jìn)制轉(zhuǎn)換實(shí)現(xiàn)方法
這篇文章主要介紹了javascript簡單進(jìn)制轉(zhuǎn)換實(shí)現(xiàn)方法,涉及javascript字符串轉(zhuǎn)換與數(shù)值操作相關(guān)技巧,需要的朋友可以參考下2016-11-11JavaScript Array實(shí)例方法flat的實(shí)現(xiàn)
flat() 方法用于將一個嵌套多層的數(shù)組進(jìn)行扁平,返回新數(shù)組,它不會改變原始數(shù)組, flat 方法在處理多維數(shù)組時非常有用,它可以讓數(shù)組操作變得更加靈活和簡潔,本文給大家介紹了JavaScript Array實(shí)例方法flat的實(shí)現(xiàn),需要的朋友可以參考下2024-03-03JavaScript數(shù)據(jù)結(jié)構(gòu)與算法之二叉樹實(shí)現(xiàn)查找最小值、最大值、給定值算法示例
這篇文章主要介紹了JavaScript數(shù)據(jù)結(jié)構(gòu)與算法之二叉樹實(shí)現(xiàn)查找最小值、最大值、給定值算法,涉及javascript二叉樹定義、賦值、遍歷、查找等相關(guān)操作技巧,需要的朋友可以參考下2019-03-03JS 文字符串轉(zhuǎn)換unicode編碼函數(shù)
AJAX傳遞中文字符串時必須把中文字符串編碼成unicode,一般會用到JS的自帶函數(shù)escape().不過找到了更好的函數(shù)來確決中文字符轉(zhuǎn)換成unicode編碼的函數(shù)2009-05-05