国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

Javascript WeakMap and WeakSet(弱映射和弱集合)

2023-02-17 10:49 更新

我們從前面的 垃圾回收 章節(jié)中知道,JavaScript 引擎在值“可達(dá)”和可能被使用時(shí)會(huì)將其保持在內(nèi)存中。

例如:

let john = { name: "John" };

// 該對(duì)象能被訪問(wèn),john 是它的引用

// 覆蓋引用
john = null;

// 該對(duì)象將會(huì)被從內(nèi)存中清除

通常,當(dāng)對(duì)象、數(shù)組之類(lèi)的數(shù)據(jù)結(jié)構(gòu)在內(nèi)存中時(shí),它們的子元素,如對(duì)象的屬性、數(shù)組的元素都被認(rèn)為是可達(dá)的。

例如,如果把一個(gè)對(duì)象放入到數(shù)組中,那么只要這個(gè)數(shù)組存在,那么這個(gè)對(duì)象也就存在,即使沒(méi)有其他對(duì)該對(duì)象的引用。

就像這樣:

let john = { name: "John" };

let array = [ john ];

john = null; // 覆蓋引用

// 前面由 john 所引用的那個(gè)對(duì)象被存儲(chǔ)在了 array 中
// 所以它不會(huì)被垃圾回收機(jī)制回收
// 我們可以通過(guò) array[0] 獲取到它

類(lèi)似的,如果我們使用對(duì)象作為常規(guī) Map 的鍵,那么當(dāng) Map 存在時(shí),該對(duì)象也將存在。它會(huì)占用內(nèi)存,并且不會(huì)被(垃圾回收機(jī)制)回收。

例如:

let john = { name: "John" };

let map = new Map();
map.set(john, "...");

john = null; // 覆蓋引用

// john 被存儲(chǔ)在了 map 中,
// 我們可以使用 map.keys() 來(lái)獲取它

WeakMap 在這方面有著根本上的不同。它不會(huì)阻止垃圾回收機(jī)制對(duì)作為鍵的對(duì)象(key object)的回收。

讓我們通過(guò)例子來(lái)看看這指的到底是什么。

WeakMap

WeakMap 和 Map 的第一個(gè)不同點(diǎn)就是,WeakMap 的鍵必須是對(duì)象,不能是原始值:

let weakMap = new WeakMap();

let obj = {};

weakMap.set(obj, "ok"); // 正常工作(以對(duì)象作為鍵)

// 不能使用字符串作為鍵
weakMap.set("test", "Whoops"); // Error,因?yàn)?"test" 不是一個(gè)對(duì)象

現(xiàn)在,如果我們?cè)?weakMap 中使用一個(gè)對(duì)象作為鍵,并且沒(méi)有其他對(duì)這個(gè)對(duì)象的引用 —— 該對(duì)象將會(huì)被從內(nèi)存(和map)中自動(dòng)清除。

let john = { name: "John" };

let weakMap = new WeakMap();
weakMap.set(john, "...");

john = null; // 覆蓋引用

// john 被從內(nèi)存中刪除了!

與上面常規(guī)的 Map 的例子相比,現(xiàn)在如果 john 僅僅是作為 WeakMap 的鍵而存在 —— 它將會(huì)被從 map(和內(nèi)存)中自動(dòng)刪除。

WeakMap 不支持迭代以及 keys()values() 和 entries() 方法。所以沒(méi)有辦法獲取 WeakMap 的所有鍵或值。

WeakMap 只有以下的方法:

  • ?weakMap.get(key)?
  • ?weakMap.set(key, value)?
  • ?weakMap.delete(key)?
  • ?weakMap.has(key)?

為什么會(huì)有這種限制呢?這是技術(shù)的原因。如果一個(gè)對(duì)象丟失了其它所有引用(就像上面示例中的 john),那么它就會(huì)被垃圾回收機(jī)制自動(dòng)回收。但是在從技術(shù)的角度并不能準(zhǔn)確知道 何時(shí)會(huì)被回收。

這些都是由 JavaScript 引擎決定的。JavaScript 引擎可能會(huì)選擇立即執(zhí)行內(nèi)存清理,如果現(xiàn)在正在發(fā)生很多刪除操作,那么 JavaScript 引擎可能就會(huì)選擇等一等,稍后再進(jìn)行內(nèi)存清理。因此,從技術(shù)上講,WeakMap 的當(dāng)前元素的數(shù)量是未知的。JavaScript 引擎可能清理了其中的垃圾,可能沒(méi)清理,也可能清理了一部分。因此,暫不支持訪問(wèn) WeakMap 的所有鍵/值的方法。

那么,在哪里我們會(huì)需要這樣的數(shù)據(jù)結(jié)構(gòu)呢?

使用案例:額外的數(shù)據(jù)

WeakMap 的主要應(yīng)用場(chǎng)景是 額外數(shù)據(jù)的存儲(chǔ)

假如我們正在處理一個(gè)“屬于”另一個(gè)代碼的一個(gè)對(duì)象,也可能是第三方庫(kù),并想存儲(chǔ)一些與之相關(guān)的數(shù)據(jù),那么這些數(shù)據(jù)就應(yīng)該與這個(gè)對(duì)象共存亡 —— 這時(shí)候 WeakMap 正是我們所需要的利器。

我們將這些數(shù)據(jù)放到 WeakMap 中,并使用該對(duì)象作為這些數(shù)據(jù)的鍵,那么當(dāng)該對(duì)象被垃圾回收機(jī)制回收后,這些數(shù)據(jù)也會(huì)被自動(dòng)清除。

weakMap.set(john, "secret documents");
// 如果 john 消失,secret documents 將會(huì)被自動(dòng)清除

讓我們來(lái)看一個(gè)例子。

例如,我們有用于處理用戶訪問(wèn)計(jì)數(shù)的代碼。收集到的信息被存儲(chǔ)在 map 中:一個(gè)用戶對(duì)象作為鍵,其訪問(wèn)次數(shù)為值。當(dāng)一個(gè)用戶離開(kāi)時(shí)(該用戶對(duì)象將被垃圾回收機(jī)制回收),這時(shí)我們就不再需要他的訪問(wèn)次數(shù)了。

下面是一個(gè)使用 Map 的計(jì)數(shù)函數(shù)的例子:

//  visitsCount.js
let visitsCountMap = new Map(); // map: user => visits count

// 遞增用戶來(lái)訪次數(shù)
function countUser(user) {
  let count = visitsCountMap.get(user) || 0;
  visitsCountMap.set(user, count + 1);
}

下面是其他部分的代碼,可能是使用它的其它代碼:

//  main.js
let john = { name: "John" };

countUser(john); // count his visits

// 不久之后,john 離開(kāi)了
john = null;

現(xiàn)在,john 這個(gè)對(duì)象應(yīng)該被垃圾回收,但它仍在內(nèi)存中,因?yàn)樗?nbsp;visitsCountMap 中的一個(gè)鍵。

當(dāng)我們移除用戶時(shí),我們需要清理 visitsCountMap,否則它將在內(nèi)存中無(wú)限增大。在復(fù)雜的架構(gòu)中,這種清理會(huì)成為一項(xiàng)繁重的任務(wù)。

我們可以通過(guò)使用 WeakMap 來(lái)避免這樣的問(wèn)題:

//  visitsCount.js
let visitsCountMap = new WeakMap(); // weakmap: user => visits count

// 遞增用戶來(lái)訪次數(shù)
function countUser(user) {
  let count = visitsCountMap.get(user) || 0;
  visitsCountMap.set(user, count + 1);
}

現(xiàn)在我們不需要去清理 visitsCountMap 了。當(dāng) john 對(duì)象變成不可達(dá)時(shí),即便它是 WeakMap 里的一個(gè)鍵,它也會(huì)連同它作為 WeakMap 里的鍵所對(duì)應(yīng)的信息一同被從內(nèi)存中刪除。

使用案例:緩存

另外一個(gè)常見(jiàn)的例子是緩存。我們可以存儲(chǔ)(“緩存”)函數(shù)的結(jié)果,以便將來(lái)對(duì)同一個(gè)對(duì)象的調(diào)用可以重用這個(gè)結(jié)果。

為了實(shí)現(xiàn)這一點(diǎn),我們可以使用 ?Map?(非最佳方案):

//  cache.js
let cache = new Map();

// 計(jì)算并記住結(jié)果
function process(obj) {
  if (!cache.has(obj)) {
    let result = /* calculations of the result for */ obj;

    cache.set(obj, result);
  }

  return cache.get(obj);
}

// 現(xiàn)在我們?cè)谄渌募惺褂?process()

//  main.js
let obj = {/* 假設(shè)我們有個(gè)對(duì)象 */};

let result1 = process(obj); // 計(jì)算完成

// ……稍后,來(lái)自代碼的另外一個(gè)地方……
let result2 = process(obj); // 取自緩存的被記憶的結(jié)果

// ……稍后,我們不再需要這個(gè)對(duì)象時(shí):
obj = null;

alert(cache.size); // 1(啊!該對(duì)象依然在 cache 中,并占據(jù)著內(nèi)存?。?/code>

對(duì)于多次調(diào)用同一個(gè)對(duì)象,它只需在第一次調(diào)用時(shí)計(jì)算出結(jié)果,之后的調(diào)用可以直接從 cache 中獲取。這樣做的缺點(diǎn)是,當(dāng)我們不再需要這個(gè)對(duì)象的時(shí)候需要清理 cache。

如果我們用 WeakMap 替代 Map,便不會(huì)存在這個(gè)問(wèn)題。當(dāng)對(duì)象被垃圾回收時(shí),對(duì)應(yīng)緩存的結(jié)果也會(huì)被自動(dòng)從內(nèi)存中清除。

//  cache.js
let cache = new WeakMap();

// 計(jì)算并記結(jié)果
function process(obj) {
  if (!cache.has(obj)) {
    let result = /* calculate the result for */ obj;

    cache.set(obj, result);
  }

  return cache.get(obj);
}

//  main.js
let obj = {/* some object */};

let result1 = process(obj);
let result2 = process(obj);

// ……稍后,我們不再需要這個(gè)對(duì)象時(shí):
obj = null;

// 無(wú)法獲取 cache.size,因?yàn)樗且粋€(gè) WeakMap,
// 要么是 0,或即將變?yōu)?0
// 當(dāng) obj 被垃圾回收,緩存的數(shù)據(jù)也會(huì)被清除

WeakSet

WeakSet 的表現(xiàn)類(lèi)似:

  • 與 ?Set? 類(lèi)似,但是我們只能向 ?WeakSet? 添加對(duì)象(而不能是原始值)。
  • 對(duì)象只有在其它某個(gè)(些)地方能被訪問(wèn)的時(shí)候,才能留在 ?WeakSet? 中。
  • 跟 ?Set? 一樣,?WeakSet? 支持 ?add?,?has? 和 ?delete? 方法,但不支持 ?size? 和 ?keys()?,并且不可迭代。

變“弱(weak)”的同時(shí),它也可以作為額外的存儲(chǔ)空間。但并非針對(duì)任意數(shù)據(jù),而是針對(duì)“是/否”的事實(shí)。WeakSet 的元素可能代表著有關(guān)該對(duì)象的某些信息。

例如,我們可以將用戶添加到 WeakSet 中,以追蹤訪問(wèn)過(guò)我們網(wǎng)站的用戶:

let visitedSet = new WeakSet();

let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };

visitedSet.add(john); // John 訪問(wèn)了我們
visitedSet.add(pete); // 然后是 Pete
visitedSet.add(john); // John 再次訪問(wèn)

// visitedSet 現(xiàn)在有兩個(gè)用戶了

// 檢查 John 是否來(lái)訪過(guò)?
alert(visitedSet.has(john)); // true

// 檢查 Mary 是否來(lái)訪過(guò)?
alert(visitedSet.has(mary)); // false

john = null;

// visitedSet 將被自動(dòng)清理(即自動(dòng)清除其中已失效的值 john)

WeakMap 和 WeakSet 最明顯的局限性就是不能迭代,并且無(wú)法獲取所有當(dāng)前內(nèi)容。那樣可能會(huì)造成不便,但是并不會(huì)阻止 WeakMap/WeakSet 完成其主要工作 —— 為在其它地方存儲(chǔ)/管理的對(duì)象數(shù)據(jù)提供“額外”存儲(chǔ)。

總結(jié)

WeakMap 是類(lèi)似于 Map 的集合,它僅允許對(duì)象作為鍵,并且一旦通過(guò)其他方式無(wú)法訪問(wèn)這些對(duì)象,垃圾回收便會(huì)將這些對(duì)象與其關(guān)聯(lián)值一同刪除。

WeakSet 是類(lèi)似于 Set 的集合,它僅存儲(chǔ)對(duì)象,并且一旦通過(guò)其他方式無(wú)法訪問(wèn)這些對(duì)象,垃圾回收便會(huì)將這些對(duì)象刪除。

它們的主要優(yōu)點(diǎn)是它們對(duì)對(duì)象是弱引用,所以被它們引用的對(duì)象很容易地被垃圾收集器移除。

這是以不支持 clear、size、keys、values 等作為代價(jià)換來(lái)的……

WeakMap 和 WeakSet 被用作“主要”對(duì)象存儲(chǔ)之外的“輔助”數(shù)據(jù)結(jié)構(gòu)。一旦將對(duì)象從主存儲(chǔ)器中刪除,如果該對(duì)象僅被用作 WeakMap 或 WeakSet 的鍵,那么該對(duì)象將被自動(dòng)清除。

任務(wù)


存儲(chǔ) "unread" 標(biāo)識(shí)

重要程度: 5

這里有一個(gè) messages 數(shù)組:

let messages = [
  {text: "Hello", from: "John"},
  {text: "How goes?", from: "John"},
  {text: "See you soon", from: "Alice"}
];

你的代碼可以訪問(wèn)它,但是 message 是由其他人的代碼管理的。該代碼會(huì)定期添加新消息,刪除舊消息,但是你不知道這些操作確切的發(fā)生時(shí)間。

現(xiàn)在,你應(yīng)該使用什么數(shù)據(jù)結(jié)構(gòu)來(lái)保存關(guān)于消息“是否已讀”的信息?該結(jié)構(gòu)必須很適合對(duì)給定的 message 對(duì)象給出“它讀了嗎?”的答案。

P.S. 當(dāng)一個(gè)消息被從 ?messages? 中刪除后,它應(yīng)該也從你的數(shù)據(jù)結(jié)構(gòu)中消失。

P.S. 我們不能修改 message 對(duì)象,例如向其添加我們的屬性。因?yàn)樗鼈兪怯善渌说拇a管理的,我們修改該數(shù)據(jù)可能會(huì)導(dǎo)致不好的后果。


解決方案

讓我們將已讀消息存儲(chǔ)在 WeakSet 中:

let messages = [
  {text: "Hello", from: "John"},
  {text: "How goes?", from: "John"},
  {text: "See you soon", from: "Alice"}
];

let readMessages = new WeakSet();

// 兩個(gè)消息已讀
readMessages.add(messages[0]);
readMessages.add(messages[1]);
// readMessages 包含兩個(gè)元素

// ……讓我們?cè)僮x一遍第一條消息!
readMessages.add(messages[0]);
// readMessages 仍然有兩個(gè)不重復(fù)的元素

// 回答:message[0] 已讀?
alert("Read message 0: " + readMessages.has(messages[0])); // true

messages.shift();
// 現(xiàn)在 readMessages 有一個(gè)元素(技術(shù)上來(lái)講,內(nèi)存可能稍后才會(huì)被清理)

WeakSet 允許存儲(chǔ)一系列的消息,并且很容易就能檢查它是否包含某個(gè)消息。

它會(huì)自動(dòng)清理自身。代價(jià)是,我們不能對(duì)它進(jìn)行迭代,也不能直接從中獲取“所有已讀消息”。但是,我們可以通過(guò)遍歷所有消息,然后找出存在于 set 的那些消息來(lái)完成這個(gè)功能。

另一種不同的解決方案可以是,在讀取消息后向消息添加諸如 message.isRead=true 之類(lèi)的屬性。由于 messages 對(duì)象是由另一個(gè)代碼管理的,因此通常不建議這樣做,但是我們可以使用 symbol 屬性來(lái)避免沖突。

像這樣:

// symbol 屬性?xún)H對(duì)于我們的代碼是已知的
let isRead = Symbol("isRead");
messages[0][isRead] = true;

現(xiàn)在,第三方代碼可能看不到我們的額外屬性。

盡管 symbol 可以降低出現(xiàn)問(wèn)題的可能性,但從架構(gòu)的角度來(lái)看,還是使用 ?WeakSet? 更好。


保存閱讀日期

重要程度: 5

這兒有一個(gè)和 上一個(gè)任務(wù) 類(lèi)似的 messages 數(shù)組。場(chǎng)景也相似。

let messages = [
  {text: "Hello", from: "John"},
  {text: "How goes?", from: "John"},
  {text: "See you soon", from: "Alice"}
];

現(xiàn)在的問(wèn)題是:你建議采用什么數(shù)據(jù)結(jié)構(gòu)來(lái)保存信息:“消息是什么時(shí)候被閱讀的?”。

在前一個(gè)任務(wù)中我們只需要保存“是/否”。現(xiàn)在我們需要保存日期,并且它應(yīng)該在消息被垃圾回收時(shí)也被從內(nèi)存中清除。

P.S. 日期可以存儲(chǔ)為內(nèi)建的 Date 類(lèi)的對(duì)象,稍后我們將進(jìn)行介紹。


解決方案

我們可以使用 WeakMap 保存日期:

let messages = [
  {text: "Hello", from: "John"},
  {text: "How goes?", from: "John"},
  {text: "See you soon", from: "Alice"}
];

let readMap = new WeakMap();

readMap.set(messages[0], new Date(2017, 1, 1));
// 我們稍后將學(xué)習(xí) Date 對(duì)象


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)