Set、Map、WeakSet 和 WeakMap 的區別

Set 和 Map 主要的應用場景在於 數據重組 和 數據儲存git

Set 是一種叫作集合的數據結構,Map 是一種叫作字典的數據結構github

1. 集合(Set)

ES6 新增的一種新的數據結構,相似於數組,但成員是惟一且無序的,沒有重複的值。算法

Set 自己是一種構造函數,用來生成 Set 數據結構。json

new Set([iterable])

舉個例子:數組

const s = new Set()
[1, 2, 3, 4, 3, 2, 1].forEach(x => s.add(x))

for (let i of s) {
    console.log(i)	// 1 2 3 4
}

// 去重數組的重複對象
let arr = [1, 2, 3, 2, 1, 1]
[... new Set(arr)]	// [1, 2, 3]

Set 對象容許你儲存任何類型的惟一值,不管是原始值或者是對象引用。數據結構

向 Set 加入值的時候,不會發生類型轉換,因此5"5"是兩個不一樣的值。Set 內部判斷兩個值是否不一樣,使用的算法叫作「Same-value-zero equality」,它相似於精確相等運算符(===),主要的區別是**NaN等於自身,而精確相等運算符認爲NaN不等於自身。**函數

let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}

let set1 = new Set()
set1.add(5)
set1.add('5')
console.log([...set1])	// [5, "5"]
  • Set 實例屬性this

    • constructor: 構造函數spa

    • size:元素數量prototype

      let set = new Set([1, 2, 3, 2, 1])
      
      console.log(set.length)	// undefined
      console.log(set.size)	// 3
  • Set 實例方法

    • 操做方法
      • add(value):新增,至關於 array裏的push

      • delete(value):存在即刪除集合中value

      • has(value):判斷集合中是否存在 value

      • clear():清空集合


        let set = new Set()
        set.add(1).add(2).add(1)
        
        set.has(1)	// true
        set.has(3)	// false
        set.delete(1)	
        set.has(1)	// false

        Array.from 方法能夠將 Set 結構轉爲數組

        const items = new Set([1, 2, 3, 2])
        const array = Array.from(items)
        console.log(array)	// [1, 2, 3]
        // 或
        const arr = [...items]
        console.log(arr)	// [1, 2, 3]
    • 遍歷方法(遍歷順序爲插入順序)
      • keys():返回一個包含集合中全部鍵的迭代器

      • values():返回一個包含集合中全部值得迭代器

      • entries():返回一個包含Set對象中全部元素得鍵值對迭代器

      • forEach(callbackFn, thisArg):用於對集合成員執行callbackFn操做,若是提供了 thisArg 參數,回調中的this會是這個參數,沒有返回值

        let set = new Set([1, 2, 3])
        console.log(set.keys())	// SetIterator {1, 2, 3}
        console.log(set.values())	// SetIterator {1, 2, 3}
        console.log(set.entries())	// SetIterator {1, 2, 3}
        
        for (let item of set.keys()) {
          console.log(item);
        }	// 1	2	 3
        for (let item of set.entries()) {
          console.log(item);
        }	// [1, 1]	[2, 2]	[3, 3]
        
        set.forEach((value, key) => {
            console.log(key + ' : ' + value)
        })	// 1 : 1	2 : 2	3 : 3
        console.log([...set])	// [1, 2, 3]

        Set 可默認遍歷,默認迭代器生成函數是 values() 方法

        Set.prototype[Symbol.iterator] === Set.prototype.values	// true

        因此, Set可使用 map、filter 方法

        let set = new Set([1, 2, 3])
        set = new Set([...set].map(item => item * 2))
        console.log([...set])	// [2, 4, 6]
        
        set = new Set([...set].filter(item => (item >= 4)))
        console.log([...set])	//[4, 6]

        所以,Set 很容易實現交集(Intersect)、並集(Union)、差集(Difference)

        let set1 = new Set([1, 2, 3])
        let set2 = new Set([4, 3, 2])
        
        let intersect = new Set([...set1].filter(value => set2.has(value)))
        let union = new Set([...set1, ...set2])
        let difference = new Set([...set1].filter(value => !set2.has(value)))
        
        console.log(intersect)	// Set {2, 3}
        console.log(union)		// Set {1, 2, 3, 4}
        console.log(difference)	// Set {1}

2. WeakSet

WeakSet 對象容許你將弱引用對象儲存在一個集合中

WeakSet 與 Set 的區別:

  • WeakSet 只能儲存對象引用,不能存放值,而 Set 對象均可以
  • WeakSet 對象中儲存的對象值都是被弱引用的,即垃圾回收機制不考慮 WeakSet 對該對象的應用,若是沒有其餘的變量或屬性引用這個對象值,則這個對象將會被垃圾回收掉(不考慮該對象還存在於 WeakSet 中),因此,WeakSet 對象裏有多少個成員元素,取決於垃圾回收機制有沒有運行,運行先後成員個數可能不一致,遍歷結束以後,有的成員可能取不到了(被垃圾回收了),WeakSet 對象是沒法被遍歷的(ES6 規定 WeakSet 不可遍歷),也沒有辦法拿到它包含的全部元素

屬性:

  • constructor:構造函數,任何一個具備 Iterable 接口的對象,均可以做參數

    const arr = [[1, 2], [3, 4]]
    const weakset = new WeakSet(arr)
    console.log(weakset)

方法:

  • add(value):在WeakSet 對象中添加一個元素value
  • has(value):判斷 WeakSet 對象中是否包含value
  • delete(value):刪除元素 value
  • clear():清空全部元素,注意該方法已廢棄
var ws = new WeakSet()
var obj = {}
var foo = {}

ws.add(window)
ws.add(obj)

ws.has(window)	// true
ws.has(foo)	// false

ws.delete(window)	// true
ws.has(window)	// false

3. 字典(Map)

集合 與 字典 的區別:

  • 共同點:集合、字典 能夠儲存不重複的值
  • 不一樣點:集合 是以 [value, value]的形式儲存元素,字典 是以 [key, value] 的形式儲存
const m = new Map()
const o = {p: 'haha'}
m.set(o, 'content')
m.get(o)	// content

m.has(o)	// true
m.delete(o)	// true
m.has(o)	// false

任何具備 Iterator 接口、且每一個成員都是一個雙元素的數組的數據結構均可以看成Map構造函數的參數,例如:

const set = new Set([
  ['foo', 1],
  ['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1

const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3

若是讀取一個未知的鍵,則返回undefined

new Map().get('asfddfsasadf')
// undefined

注意,只有對同一個對象的引用,Map 結構纔將其視爲同一個鍵。這一點要很是當心。

const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined

上面代碼的setget方法,表面是針對同一個鍵,但實際上這是兩個值,內存地址是不同的,所以get方法沒法讀取該鍵,返回undefined

由上可知,Map 的鍵其實是跟內存地址綁定的,只要內存地址不同,就視爲兩個鍵。這就解決了同名屬性碰撞(clash)的問題,咱們擴展別人的庫的時候,若是使用對象做爲鍵名,就不用擔憂本身的屬性與原做者的屬性同名。

若是 Map 的鍵是一個簡單類型的值(數字、字符串、布爾值),則只要兩個值嚴格相等,Map 將其視爲一個鍵,好比0-0就是一個鍵,布爾值true和字符串true則是兩個不一樣的鍵。另外,undefinednull也是兩個不一樣的鍵。雖然NaN不嚴格相等於自身,但 Map 將其視爲同一個鍵。

let map = new Map();

map.set(-0, 123);
map.get(+0) // 123

map.set(true, 1);
map.set('true', 2);
map.get(true) // 1

map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3

map.set(NaN, 123);
map.get(NaN) // 123

Map 的屬性及方法

屬性:

  • constructor:構造函數

  • size:返回字典中所包含的元素個數

    const map = new Map([
      ['name', 'An'],
      ['des', 'JS']
    ]);
    
    map.size // 2

操做方法:

  • set(key, value):向字典中添加新元素
  • get(key):經過鍵查找特定的數值並返回
  • has(key):判斷字典中是否存在鍵key
  • delete(key):經過鍵 key 從字典中移除對應的數據
  • clear():將這個字典中的全部元素刪除

遍歷方法

  • Keys():將字典中包含的全部鍵名以迭代器形式返回
  • values():將字典中包含的全部數值以迭代器形式返回
  • entries():返回全部成員的迭代器
  • forEach():遍歷字典的全部成員
const map = new Map([
            ['name', 'An'],
            ['des', 'JS']
        ]);
console.log(map.entries())	// MapIterator {"name" => "An", "des" => "JS"}
console.log(map.keys()) // MapIterator {"name", "des"}

Map 結構的默認遍歷器接口(Symbol.iterator屬性),就是entries方法。

map[Symbol.iterator] === map.entries
// true

Map 結構轉爲數組結構,比較快速的方法是使用擴展運算符(...)。

對於 forEach ,看一個例子

const reporter = {
  report: function(key, value) {
    console.log("Key: %s, Value: %s", key, value);
  }
};

let map = new Map([
    ['name', 'An'],
    ['des', 'JS']
])
map.forEach(function(value, key, map) {
  this.report(key, value);
}, reporter);
// Key: name, Value: An
// Key: des, Value: JS

在這個例子中, forEach 方法的回調函數的 this,就指向 reporter

與其餘數據結構的相互轉換

  1. Map 轉 Array

    const map = new Map([[1, 1], [2, 2], [3, 3]])
    console.log([...map])	// [[1, 1], [2, 2], [3, 3]]
  2. Array 轉 Map

    const map = new Map([[1, 1], [2, 2], [3, 3]])
    console.log(map)	// Map {1 => 1, 2 => 2, 3 => 3}
  3. Map 轉 Object

    由於 Object 的鍵名都爲字符串,而Map 的鍵名爲對象,因此轉換的時候會把非字符串鍵名轉換爲字符串鍵名。

    function mapToObj(map) {
        let obj = Object.create(null)
        for (let [key, value] of map) {
            obj[key] = value
        }
        return obj
    }
    const map = new Map().set('name', 'An').set('des', 'JS')
    mapToObj(map)  // {name: "An", des: "JS"}
  4. Object 轉 Map

    function objToMap(obj) {
        let map = new Map()
        for (let key of Object.keys(obj)) {
            map.set(key, obj[key])
        }
        return map
    }
    
    objToMap({'name': 'An', 'des': 'JS'}) // Map {"name" => "An", "des" => "JS"}
  5. Map 轉 JSON

    function mapToJson(map) {
        return JSON.stringify([...map])
    }
    
    let map = new Map().set('name', 'An').set('des', 'JS')
    mapToJson(map)	// [["name","An"],["des","JS"]]
  6. JSON 轉 Map

    function jsonToStrMap(jsonStr) {
      return objToMap(JSON.parse(jsonStr));
    }
    
    jsonToStrMap('{"name": "An", "des": "JS"}') // Map {"name" => "An", "des" => "JS"}

4. WeakMap

WeakMap 對象是一組鍵值對的集合,其中的鍵是弱引用對象,而值能夠是任意

注意,WeakMap 弱引用的只是鍵名,而不是鍵值。鍵值依然是正常引用。

WeakMap 中,每一個鍵對本身所引用對象的引用都是弱引用,在沒有其餘引用和該鍵引用同一對象,這個對象將會被垃圾回收(相應的key則變成無效的),因此,WeakMap 的 key 是不可枚舉的。

屬性:

  • constructor:構造函數

方法:

  • has(key):判斷是否有 key 關聯對象
  • get(key):返回key關聯對象(沒有則則返回 undefined)
  • set(key):設置一組key關聯對象
  • delete(key):移除 key 的關聯對象
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();

myWeakmap.set(myElement, {timesClicked: 0});

myElement.addEventListener('click', function() {
  let logoData = myWeakmap.get(myElement);
  logoData.timesClicked++;
}, false);

5. 總結

  • Set
    • 成員惟1、無序且不重複
    • [value, value],鍵值與鍵名是一致的(或者說只有鍵值,沒有鍵名)
    • 能夠遍歷,方法有:add、delete、has
  • WeakSet
    • 成員都是對象
    • 成員都是弱引用,能夠被垃圾回收機制回收,能夠用來保存DOM節點,不容易形成內存泄漏
    • 不能遍歷,方法有add、delete、has
  • Map
    • 本質上是鍵值對的集合,相似集合
    • 能夠遍歷,方法不少能夠跟各類數據格式轉換
  • WeakMap
    • 只接受對象做爲鍵名(null除外),不接受其餘類型的值做爲鍵名
    • 鍵名是弱引用,鍵值能夠是任意的,鍵名所指向的對象能夠被垃圾回收,此時鍵名是無效的
    • 不能遍歷,方法有get、set、has、delete

6. 擴展:Object與Set、Map

  1. Object 與 Set

    // Object
    const properties1 = {
        'width': 1,
        'height': 1
    }
    console.log(properties1['width']? true: false) // true
    
    // Set
    const properties2 = new Set()
    properties2.add('width')
    properties2.add('height')
    console.log(properties2.has('width')) // true
  2. Object 與 Map

JS 中的對象(Object),本質上是鍵值對的集合(hash 結構)

const data = {};
const element = document.getElementsByClassName('App');

data[element] = 'metadata';
console.log(data['[object HTMLCollection]']) // "metadata"

但當以一個DOM節點做爲對象 data 的鍵,對象會被自動轉化爲字符串[Object HTMLCollection],因此說,Object 結構提供了 字符串-值 對應,Map則提供了 值-值 的對應

相關文章
相關標籤/搜索