七: ES6 Set Map

前言

該部分為書(shū)籍 深入理解ES6 第七章(Set與Map)筆記

ES5中的 Set 與 Map

  • 在 ES5 中, 一般使用對(duì)象屬性來(lái)模擬 Set 與 Map

    let set = Object.create(null);
    
    set.foo = true;
    
    // 檢查屬性的存在性
    if (set.foo) {
        
        // 一些操作
    }
    
  • 使用對(duì)象模擬 Map 與 Set 之間唯一真正的區(qū)別是所存儲(chǔ)的值

    與 Set 不同, Map 多數(shù)被用來(lái)提取數(shù)據(jù),而不是僅檢查鍵的存在性。

    let map = Object.create(null);
    
    map.foo = "bar";
    
    // 提取一個(gè)值
    let value = map.foo;
    
    console.log(value); // "bar"
    

變通方法的問(wèn)題

  1. 由于對(duì)象屬性的類(lèi)型必須為字符串,你就必須保證任意兩個(gè)鍵不能被轉(zhuǎn)換為相同的字符串。

    let map = Object.create(null);
    
    map[5] = "foo";
    
    console.log(map["5"]); // "foo"
    
  2. 若使用對(duì)象作為鍵, 就會(huì)出現(xiàn)另一個(gè)問(wèn)題(對(duì)象會(huì)被轉(zhuǎn)換為字符串[object Object])

ES6的 Set

ES6 新增了 Set 類(lèi)型, 這是一種無(wú)重復(fù)值的有序列表.(Set 不是一種基礎(chǔ)類(lèi)型, 還是 Object 類(lèi)型之上封裝的一種數(shù)據(jù)結(jié)構(gòu), [[class]] 屬性為 "Set")

Set 允許對(duì)它包含的數(shù)據(jù)進(jìn)行快速訪問(wèn),從而增加了一個(gè)追蹤離散值的更有效方式。

1. 創(chuàng)建 Set

  • 使用 new Set()來(lái)創(chuàng)建

    在 Set 內(nèi)部的比較使用了 Object.is() 方法, 來(lái)判斷兩個(gè)值是否相等, 唯一的例外是 +0 與-0 在 Set 中被判斷為是相等的

    // 參數(shù): iterable
    // 如果傳遞一個(gè)可迭代對(duì)象,它的所有元素將不重復(fù)地被添加到新的 Set中。如果不指定此參數(shù)或其值為null,則新的 Set為空。
    
    let set = new Set([iterable]?);
    

2. 方法以及屬性

  1. Set.prototype.add(value): 添加項(xiàng)目

    如果 add() 方法用相同值進(jìn)行了多次調(diào)用,那么在第一次之后的調(diào)用實(shí)際上會(huì)被忽略

    set.add(5);
    
  2. Set.prototype.delete(value): 刪除值

    移除Set的中與這個(gè)值相等的元素,返回Set.prototype.has(value)在這個(gè)操作前會(huì)返回的值(即如果該元素存在,返回true,否則返回false)。Set.prototype.has(value)在此后會(huì)返回false。

  3. Set.prototype.clear():刪除所有值

  4. Set.prototype.has(value): 判斷值是否存在

  5. Set.prototype.size: 屬性, 返回Set對(duì)象的值的個(gè)數(shù)。

3. 迭代方法

  1. Set.prototype.forEach(callbackFn[, thisArg])

    因?yàn)?Set 并沒(méi)有數(shù)組或者對(duì)象概念的索引或鍵, 為了與其他數(shù)據(jù)結(jié)構(gòu)的 forEach 保持一致, 回調(diào)函數(shù)的第一個(gè)與第二個(gè)參數(shù)是相同的

    要記住,雖然 Set 能非常好地追蹤值,并且 forEach() 可以讓你按順序處理每一項(xiàng),但是卻無(wú)法像數(shù)組那樣用索引來(lái)直接訪問(wèn)某個(gè)值。

    let set = new Set([1, 2]);
    
    /*
     * 回調(diào)函數(shù)接受三個(gè)參數(shù): 
     * Set 中下個(gè)位置的值
     * 與第一個(gè)參數(shù)相同的值
     * 目標(biāo) Set 自身
    */
    set.forEach(function(value, key, ownerSet) {
     console.log(key + " " + value);
     console.log(ownerSet === set);
    });
    
  2. Set.prototype.keys()

    與values()方法相同,返回一個(gè)新的迭代器對(duì)象,該對(duì)象包含Set對(duì)象中的按插入順序排列的所有元素的值。

  3. Set.prototype.values()

    返回一個(gè)新的迭代器對(duì)象,該對(duì)象包含Set對(duì)象中的按插入順序排列的所有元素的值。

  4. Set.prototype.entries()

    返回一個(gè)新的迭代器對(duì)象,該對(duì)象包含Set對(duì)象中的按插入順序排列的所有元素的值的[value, value]數(shù)組。為了使這個(gè)方法和Map對(duì)象保持相似, 每個(gè)值的鍵和值相等。

4.將 Set 轉(zhuǎn)化為數(shù)組

  • 數(shù)組轉(zhuǎn) Set

    直接將數(shù)組傳遞給 Set 構(gòu)造器

  • Set轉(zhuǎn) 數(shù)組

    使用 擴(kuò)展運(yùn)算符

let set = new Set([1, 2, 3, 3, 3, 4, 5]),
array = [...set];

console.log(array); // [1,2,3,4,5]

可以用這個(gè)來(lái)快速進(jìn)行數(shù)組去重

function eliminateDuplicates(items) {
    return [...new Set(items)];
}

ES6中 Weak Set

由于 Set 類(lèi)型存儲(chǔ)對(duì)象引用的方式,它也可以被稱(chēng)為 Strong Set 。對(duì)象存儲(chǔ)在 Set 的一個(gè)實(shí)例中時(shí),實(shí)際上相當(dāng)于把對(duì)象存儲(chǔ)在變量中。只要對(duì)于 Set 實(shí)例的引用仍然存在,所存儲(chǔ)的對(duì)象就無(wú)法被垃圾回收機(jī)制回收,從而無(wú)法釋放內(nèi)存。

let set = new Set(),
    key = {};

set.add(key);
console.log(set.size); // 1

// 取消原始引用
// 將 key 設(shè)置為 null 清除了對(duì) key 對(duì)象的一個(gè)引用,但是另一個(gè)引用還存于set 內(nèi)部
key = null;

console.log(set.size); // 1

// 重新獲得原始引用
key = [...set][0];

為了緩解這個(gè)問(wèn)題, ES6 也包含了 Weak Set ,該類(lèi)型只允許存儲(chǔ)對(duì)象弱引用,而不能存儲(chǔ)基本類(lèi)型的值。對(duì)象的弱引用在它自己成為該對(duì)象的唯一引用時(shí),不會(huì)阻止垃圾回收

1. 創(chuàng)建 Weak Set

  • 使用 new WeakSet()構(gòu)造器

    **注意: ** Weak Set 項(xiàng)不能存在非對(duì)象的值, 如果傳入了非對(duì)象的值, 就會(huì)拋出錯(cuò)誤

    // [iterable]: 如果傳入一個(gè)可迭代對(duì)象作為參數(shù), 則該對(duì)象的所有迭代值都會(huì)被自動(dòng)添加進(jìn)生成的 WeakSet 對(duì)象中。null 被認(rèn)為是 undefined。
    let set = new WeakSet([iterable])
    
  • 方法

    1. WeakSet.prototype.add(value): 添加項(xiàng)
    2. WeakSet.prototype.has(value): 判斷項(xiàng)
    3. WeakSet.prototype.delete(value): 刪除項(xiàng)

2. 與 Set 類(lèi)型的差異

Weak Set 看起來(lái)功能有限,而這對(duì)于正確管理內(nèi)存而言是必要的。一般來(lái)說(shuō),若只想追蹤對(duì)象的引用,應(yīng)當(dāng)使用 Weak Set 而不是正規(guī) Set 。

  1. 最大區(qū)別是對(duì)象的弱引用(即當(dāng)其他對(duì)象沒(méi)有引用時(shí), 就會(huì)被環(huán)境所回收)

    JS 引擎垃圾回收機(jī)制最常見(jiàn)的就是標(biāo)記清除法(可參考JavaScript 高級(jí)程序設(shè)計(jì)書(shū))

  2. 對(duì)于 WeakSet 的實(shí)例,若調(diào)用 add() 方法時(shí)傳入了非對(duì)象的參數(shù),就會(huì)拋出錯(cuò)誤(has() 或 delete() 則會(huì)在傳入了非對(duì)象的參數(shù)時(shí)返回 false );

  3. Weak Set 不可迭代,因此不能被用在 for-of 循環(huán)中;

  4. Weak Set 無(wú)法暴露出任何迭代器(例如 keys() 與 values() 方法),因此沒(méi)有任何編程手段可用于判斷 Weak Set 的內(nèi)容;

  5. Weak Set 沒(méi)有 forEach() 方法;

  6. Weak Set 沒(méi)有 size 屬性。

ES6中的Map

ES6的 Map 類(lèi)型是鍵值對(duì)的有序列表, 而鍵和值都可以是任意類(lèi)型 . 鍵的比較使用的是 Object.is(), 因此 5"5" 同時(shí)作為鍵, 因?yàn)樗鼈冾?lèi)型不同.

1. 創(chuàng)建 Map

  • new Map([iterable])

    在方法內(nèi)部使用的是 Object.is() , 與 new Set() 類(lèi)似

    // Iterable 可以是一個(gè)數(shù)組或者其他 iterable 對(duì)象,其元素為鍵值對(duì)(兩個(gè)元素的數(shù)組,例如: [[ 1, 'one' ],[ 2, 'two' ]])。 每個(gè)鍵值對(duì)都會(huì)添加到新的 Map。null 會(huì)被當(dāng)做 undefined。
    
    let set = new Set([iterable]?);
    

2. 方法 和 屬性

  1. Map.prototype.size: 返回Map對(duì)象的鍵/值對(duì)的數(shù)量。

  2. Map.prototype.get(key): 返回鍵對(duì)應(yīng)的值,如果不存在,則返回undefined。

  3. Map.prototype.set(key, value): 設(shè)置Map對(duì)象中鍵的值。返回該Map對(duì)象。

  4. Map.prototype.has(key): 返回一個(gè)布爾值,表示Map實(shí)例是否包含鍵對(duì)應(yīng)的值。

  5. Map.prototype.delete(key): 刪除項(xiàng)

    如果 Map 對(duì)象中存在該元素,則移除它并返回 true;否則如果該元素不存在則返回 false。隨后調(diào)用 Map.prototype.has(key) 將返回 false 。

  6. Map.prototype.clear(): 移除Map對(duì)象的所有鍵/值對(duì) 。

3. 迭代方法

  1. Map.prototype.forEach(callbackFn[, thisArg]): 按插入順序,為 Map對(duì)象里的每一鍵值對(duì)調(diào)用一次callbackFn函數(shù)。如果為forEach提供了thisArg,它將在每次回調(diào)中作為this值。
  2. Map.prototype.keys(): 返回一個(gè)新的 Iterator對(duì)象, 它按插入順序包含了Map對(duì)象中每個(gè)元素的鍵 。
  3. Map.prototype.values():返回一個(gè)新的Iterator對(duì)象,它按插入順序包含了Map對(duì)象中每個(gè)元素的值 。
  4. Map.prototype.entries(): 返回一個(gè)新的 Iterator 對(duì)象,它按插入順序包含了Map對(duì)象中每個(gè)元素的 [key, value] 數(shù)組。

4. 與普通對(duì)象(Object)的區(qū)別

  • 一個(gè)Object的鍵只能是字符串或者 Symbols,但一個(gè) Map 的鍵可以是任意值,包括函數(shù)、對(duì)象、基本類(lèi)型。

  • Map 中的鍵值是有序的,而添加到對(duì)象中的鍵則不是。因此,當(dāng)對(duì)它進(jìn)行遍歷時(shí),Map 對(duì)象是按插入的順序返回鍵值。

    注意:自ECMAScript 2015規(guī)范以來(lái),對(duì)象確實(shí)保留了字符串和Symbol鍵的創(chuàng)建順序; 因此,在只有字符串鍵的對(duì)象上進(jìn)行迭代將按插入順序產(chǎn)生鍵。**

  • 你可以通過(guò) size 屬性直接獲取一個(gè) Map 的鍵值對(duì)個(gè)數(shù),而 Object 的鍵值對(duì)個(gè)數(shù)只能手動(dòng)計(jì)算。

  • Map 可直接進(jìn)行迭代,而 Object 的迭代需要先獲取它的鍵數(shù)組,然后再進(jìn)行迭代。

  • Object 都有自己的原型,原型鏈上的鍵名有可能和你自己在對(duì)象上的設(shè)置的鍵名產(chǎn)生沖突。

    注意:雖然 ES5 開(kāi)始可以用 map = Object.create(null) 來(lái)創(chuàng)建一個(gè)沒(méi)有原型的對(duì)象,但是這種用法不太常見(jiàn)

  • Map 在涉及頻繁增刪鍵值對(duì)的場(chǎng)景下會(huì)有些性能優(yōu)勢(shì)。

ES6中的 Weak Map

Weak Map 對(duì) Map 而言,就像 Weak Set 對(duì) Set 一樣: Weak 版本都是存儲(chǔ)對(duì)象弱引用的方式。在 Weak Map 中,所有的鍵都必須是對(duì)象(嘗試使用非對(duì)象的鍵會(huì)拋出錯(cuò)誤),而且這些對(duì)象都是弱引用,不會(huì)干擾垃圾回收。當(dāng) Weak Map 中的鍵在 Weak Map 之外不存在引用時(shí),該鍵值對(duì)會(huì)被移除。

必須注意的是, Weak Map 的鍵才是弱引用,而值不是。在 Weak Map 的值中存儲(chǔ)對(duì)象會(huì)阻止垃圾回收,即使該對(duì)象的其他引用已全都被移除。

1. 創(chuàng)建 Weak Map

  • 使用 new WeakMap() 構(gòu)造器

    鍵必須是非空的對(duì)象, 否則會(huì)拋出錯(cuò)誤

    值則允許是任意類(lèi)型

    // Iterable 是一個(gè)數(shù)組(二元數(shù)組)或者其他可迭代的且其元素是鍵值對(duì)的對(duì)象。每個(gè)鍵值對(duì)會(huì)被加到新的 WeakMap 里。null 會(huì)被當(dāng)做 undefined。
    new WeakMap([iterable]?)
    
  • 方法

    1. WeakMap.prototype.set(key, value): 在WeakMap中設(shè)置一組key關(guān)聯(lián)對(duì)象,返回這個(gè) WeakMap對(duì)象。
    2. WeakMap.prototype.get(key): 返回key關(guān)聯(lián)對(duì)象, 或者 undefined(沒(méi)有key關(guān)聯(lián)對(duì)象時(shí))。
    3. WeakMap.prototype.has(key): 根據(jù)是否有key關(guān)聯(lián)對(duì)象返回一個(gè)Boolean值。
    4. WeakMap.prototype.delete(key): 移除key的關(guān)聯(lián)對(duì)象。執(zhí)行后 WeakMap.prototype.has(key)返回false。

2. Weak Map 的用法與局限性

當(dāng)決定是要使用 Weak Map 還是使用正規(guī) Map 時(shí),首要考慮因素在于你是否只想使用對(duì)象類(lèi)型的鍵。如果你打算這么做,那么最好的選擇就是 Weak Map 。因?yàn)樗艽_保額外數(shù)據(jù)在不再可用后被銷(xiāo)毀,從而能優(yōu)化內(nèi)存使用并規(guī)避內(nèi)存泄漏。

Weak Map 值為他們的內(nèi)容提供了很小的可見(jiàn)度, 因此你不能使用 forEach() 方法、size 屬性 或 clear() 方法來(lái)管理其中的項(xiàng)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容