ES6面試點-WeakMap與Map的區別,Set與WeakSet的區別

ES6引入了四種新的數據結構:

  • 映射(Map)
  • 弱映射(WeakMap)
  • 集合(Set)
  • 弱集合(WeakSet)

一、Object 對比 Map
Object作爲哈希表使用存在以下問題

Object的key必須是String或者是Symbol,當key不爲字符串時,會調用toString()進行強制轉換,將轉換後的字符串作爲key

Object含有內置屬性,如constructor、toString、valueOf,與其同名的鍵值會產生衝突,可以使用Object.create(null)創建一個空對象繼承自null來避免此問題

Object其屬性可能是不可遍歷的、有些屬性可能是在原型鏈上,所以Object長度的獲取比較繁瑣

Object是不可迭代的,即不能使用for…of來遍歷,

typeof obj[Symbol.iterator] === undefined

Object是無序的,其元素順序與添加的順序無關

Object的使用

創建

var obj = new Object();            //創建一個空對象
var obj = Object.create(null);    //obj繼承null

添加

obj['age'] = 11;
obj.age = 11; 

獲取

obj.age;   //11
obj['age'];  //11

判斷
某個key是否存在相應的value

if(obj.age !== undefined) {}     //判斷存在
if('age' in obj) {}                        //判斷存在

刪除
delete obj.age; //刪除徹底該屬性
obj.age = undefined; //僅僅是改變了值爲undefined,

該對象仍然保留有該屬性
獲取大小

Object.keys(obj).length; 

Object.keys只返回對象自身的可遍歷屬性的全部屬性名,
不包括原型鏈上的屬性
遍歷

// obj {age: 11, name: "jack"}
for (var key in obj){
   console.log(`key: ${key}, value: ${obj[key]}`);
   //key: age, value: 11
   //key: name, value: jack
}
Object.keys(obj).forEach((key)=> console.log(`key: ${key}, value: ${obj[key]}`));
//key: age, value: 11
//key: name, value: jack

在這裏插入圖片描述
Map更適合用來做哈希表

各種類型的值(包括object)都可以作爲key

Map支持迭代,直接使用for…of來遍歷,而不需要像對象一樣先獲取key再遍歷

Map在遇到頻繁刪除添加和鍵值對的場景下,有更好的性能表現

Map用迭代的方式遍歷key時,得到的key的順序與key添加到Map時的順序相同

Map的使用方式

創建

var map = new Map();   //空Map
var map = new Map([[1,2],[2,3]]); 
 // map = {1=>2, 2=>3}

在這裏插入圖片描述
添加

map.set(4,5);     //map = {4=>5},

但添加的key已存在相應的value,則覆蓋舊value
在這裏插入圖片描述

獲取
map.get(key); //若相應的value不存在則返回undefined
在這裏插入圖片描述
判斷某個key是否存在相應的value

map.has(key); // 通過key判斷,返回一個boolean值
在這裏插入圖片描述
刪除

map.delete(key); //刪除成功返回true,返回false表示該屬性不存在
在這裏插入圖片描述
map.clear(); //刪除所有元素
在這裏插入圖片描述
獲取大小
map.size;
在這裏插入圖片描述
遍歷
//map: { 2=>3, 4=>5}
for (const item of map){
console.log(item);
//Array[2,3]
//Array[4,5]
}
//或者
for (const [key,value] of map){
console.log(key: ${key}, value: ${value});
//key: 2, value: 3
//key: 4, value: 5
}
//或者
map.forEach((value, key) => console.log(key: ${key}, value: ${value}));
//key: 2, value: 3
//key: 4, value: 5
在這裏插入圖片描述
for in不能便利map數據結構
在這裏插入圖片描述
二、Set
類似於數組,沒有重複的元素,可迭代

代碼示例

//創建
var set = new Set();
var set = new Set([1,2,3,4]); //出了可接收數組作爲參數,也可接受具有iterable接口的其他數據結構
在這裏插入圖片描述
so…set 可用於數組快速去重
添加
set.add(key); 已經存在會被覆蓋
在這裏插入圖片描述
//刪除
set.delete(key); //刪除某個值,返回一個布爾值,表示刪除是否成功
在這裏插入圖片描述
set.clear(); //清空集合
在這裏插入圖片描述
//判斷
成員是否存在
set.has(key); //返回布爾值,表示該值是否爲成員

//獲取大小
set.size;
在這裏插入圖片描述
//遍歷
for (var item of set) {
console.log(item); // 1 2 3 4
}
在這裏插入圖片描述
同樣不能for in 遍歷

三、weakMap

什麼是WeakMap?

  • WeakMap結構與Map結構類似,也是用於生成鍵值對的集合。
  • 擁有get、has、delete等方法,使用法和使用途都一樣。
  • WeakMap只接受對象作爲鍵名,但null不能作爲鍵名
  • WeakMap不支持clear方法,不支持遍歷,也就沒有了keys、values、entries、forEach這4個方法,也沒有屬性size
  • WeakMap只接受對象作爲鍵名,但null不能作爲鍵名
  • WeakMap不支持clear方法,不支持遍歷,也就沒有了keys、values、entries、forEach這4個方法,也沒有屬性size
    // WeakMap 可以使用 set 方法添加成員
    在這裏插入圖片描述
const wm1 = new WeakMap();
const key = {foo: 1};
wm1.set(key, 2);
wm1.get(key) // 2

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
// WeakMap 也可以接受一個二維數組,
// 作爲構造函數的參數
在這裏插入圖片描述

const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"

WeakMap與Map的區別
首先,WeakMap只接受對象作爲鍵名(null除外),不接受其他類型的值作爲鍵名。

const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key

上面代碼中,如果將數值1和Symbol值作爲 WeakMap 的鍵名,都會報錯。
其次,WeakMap的鍵名所指向的對象,不計入垃圾回收機制。
WeakMap的設計目的在於,有時我們想在某個對象上面存放一些數據,但是這會形成對於這個對象的引用。請看下面的例子。

const e1 = document.getElementById('foo');//假設這個節點是找得到的
const e2 = document.getElementById('bar');
const arr = [
  [e1, 'foo 元素'],
  [e2, 'bar 元素'],
];

上面代碼中,e1和e2是兩個對象,我們通過arr數組對這兩個對象添加一些文字說明。這就形成了arr對e1和e2的引用。
一旦不再需要這兩個對象,我們就必須手動刪除這個引用,否則垃圾回收機制就不會釋放e1和e2佔用的內存。

// 不需要 e1 和 e2 的時候
// 必須手動刪除引用

arr [0] = null;
arr [1] = null;

上面這樣的寫法顯然很不方便。一旦忘了寫,就會造成內存泄露。

WeakMap 就是爲了解決這個問題而誕生的,它的鍵名所引用的對象都是弱引用,即垃圾回收機制不將該引用考慮在內。因此,只要所引用的對象的其他引用都被清除,垃圾回收機制就會釋放該對象所佔用的內存。也就是說,一旦不再需要,WeakMap 裏面的鍵名對象和所對應的鍵值對會自動消失,不用手動刪除引用。
基本上,如果你要往對象上添加數據,又不想幹擾垃圾回收機制,就可以使用 WeakMap。

應用場景是,在網頁的 DOM 元素上添加數據,就可以使用WeakMap結構。當該 DOM 元素被清除,其所對應的WeakMap記錄就會自動被移除。

const wm = new WeakMap();

const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"

上面代碼中,先新建一個 Weakmap 實例。然後,將一個 DOM 節點作爲鍵名存入該實例,並將一些附加信息作爲鍵值,一起存放在 WeakMap 裏面。這時,WeakMap 裏面對element的引用就是弱引用,不會被計入垃圾回收機制。
也就是說,上面的 DOM 節點對象的引用計數是1,而不是2。這時,一旦消除對該節點的引用,它佔用的內存就會被垃圾回收機制釋放。Weakmap 保存的這個鍵值對,也會自動消失。

總之,WeakMap的專用場合就是,它的鍵所對應的對象,可能會在將來消失。WeakMap結構有助於防止內存泄漏。
注意,WeakMap 弱引用的只是鍵名,而不是鍵值。鍵值依然是正常引用。

const wm = new WeakMap();
let key = {};
let obj = {foo: 1};

wm.set(key, obj);
obj = null;
wm.get(key)
// Object {foo: 1}

ECMAscript 中所有的參數傳遞都是按照值傳遞的
上面代碼中,鍵值obj也是值引用。所以,即使在 WeakMap 外部消除了obj的引用,WeakMap 內部的引用依然存在。

WeakMap 的語法
WeakMap 與 Map 在 API 上的區別主要是兩個,

一是沒有遍歷操作(即沒有keys()、values()和entries()方法),也沒有size屬性。

因爲沒有辦法列出所有鍵名,某個鍵名是否存在完全不可預測,跟垃圾回收機制是否運行相關。這一刻可以取到鍵名,下一刻垃圾回收機制突然運行了,這個鍵名就沒了,爲了防止出現不確定性,就統一規定不能取到鍵名。
二是無法清空,即不支持clear方法。因此,WeakMap只有四個方法可用:get()、set()、has()、delete()。

const wm = new WeakMap();

// size、forEach、clear 方法都不存在
wm.size // undefined
wm.forEach // undefined
wm.clear // undefined
WeakMap 的用途
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);

上面代碼中,myElement是一個 DOM 節點,每當發生click事件,就更新一下狀態。我們將這個狀態作爲鍵值放在 WeakMap 裏,對應的鍵名就是myElement。一旦這個 DOM 節點刪除,該狀態就會自動消失,不存在內存泄漏風險。

WeakMap 的另一個用處是部署私有屬性。

const _counter = new WeakMap();
const _action = new WeakMap();

class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  dec() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}

const c = new Countdown(2, () => console.log('DONE'));

c.dec()
c.dec()
// DONE

上面代碼中,Countdown類的兩個內部屬性_counter和_action,是實例的弱引用,所以如果刪除實例,它們也就隨之消失,不會造成內存泄漏。

四、WeakSet

WeakSet 結構與 Set 類似,但只有add、delete、has三個方法

不同之處:

WeakSet的成員只能是對象,並且WeakSet不支持clear方法,不支持遍歷,也沒有forEach這個方法,沒有屬性size

WeakSet 中的對象都是弱引用,即垃圾回收機制不考慮 WeakSet 對該對象的引用,如果只有WeakSet引用該對象,那麼垃圾回收機制會自動回收該對象所佔用的內存

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章