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