Set集合是一種無重復元素大的列表,開發者一般不會逐一讀取數組中的元素,也不太可能逐一訪問Set集合的每一個元素,通常是檢測所給元素是否在集合中存在。Map集合內含多組鍵值對,經常用于緩存頻繁取用的數據。
ECMAScript5中的Set和Map集合
在ES5中,常用對象屬性來模擬這兩種集合。
var set=Object.create(null);
set.foo=true;
//檢查屬性是否存在
if(set.foo){
//要執行的代碼
}
這里的set是個原型為null的對象,不繼承任何屬性。在ES5中,開發者們用類似方法檢查某個對象的屬性值是否存在。
模擬Set和Map這兩種集合對象的唯一區別是存儲的值不同。
var map=Object.create(null);
map.foo="bar";
var value=map.foo;
console.log(value);//"bar"
這段代碼將字符串"bar"存儲在map.foo中。一般來說,Set集合常被用于檢查對象中是否存在某個鍵名,而Map集合常被用來獲取已存得信息。
該解決方案的一些問題
碰到對象屬性的某些限制,上訴方法就會復雜。例如:所有對象的屬性名必須是字符串類型,必須保證每個鍵名都是字符串類型且在對象中是唯一的。
var map=Object.create(null);
map[5]="foo";
console.log(map["5"]);//"foo"
例子中屬性鍵名5被強制轉換為了"5",如果想以數字為對象鍵名就會出問題了。
var map=Object.create(null),key1={},key2={};
map[key1]="foo";
console.log(map[key2]);//"foo"
由于例子中key1和key2將被轉換為對象對應的字符串都是"[Object Object]",所以map[key2]和map[key1]其實引用同一個屬性。這種錯誤很難被發現。
對于Map集合,如果它的屬性值是假值,則要求使用布爾值的情況下會自動轉換為false。強制轉換在某些場景會出錯.
var map=Object.create(null);
map.count=0;
if(map.count){
//要執行的代碼
}
在這里如果我們只是檢查count值是否存在,則會出錯,因為0雖然存在,但是會被轉化為false,則導致出錯。
在大型軟件應用中,一旦發生這種問題將難以定位和調試.
ECMAScript6中的Set集合
ECMAScript中新增的Set類型是一種有序列表,其中含有一些相互獨立的非重復值,通過Set集合可以快速訪問里面的數據,更有效追蹤各種離散值。
創建Set集合并添加元素
let set=new Set;//創建Set集合
set.add(5);//添加元素
set.add("5");//添加元素
console.log(set.size);//2,size屬性可以返回當前的元素數量。
在Set集合中,不對所存值發生強制的類型轉換。數字5和字符"5"是獨立存在的(引擎調用Object.is()判斷)。
let set=new Set(),key1={},key2={};
set.add(key1);
set.add(key2);
console.log(set.size);//2
多次調用add()方法來傳入相同的值作為參數,后續的調用會被忽略。
let set=new Set;
set.add(5);
set.add("5");
set.add(5);
console.log(set.size);//2
Set函數可以用數組來初始化初始化,但是重讀元素會被過濾掉,保證集合元素的唯一性。
let set=new Set([1,2,3,4,5,5,5,5,5]);
console.log(set.size);//5
實際上Set構造函數可以接受所有的迭代對象作為參數。
通過has()方法可以檢測Set集合中是否含有某個值。
let set=new Set;
set.add(5);
set.add("5");
console.log(set.has(5));//true
console.log(set.has("5"));//true
console.log(set.has(6));//false,沒有的元素會是false
移除元素
通過delete()可以移除Set集合中的某一元素,調用clear()方法會移除所有元素。
let set =new Set();
set.add(5);
set.add("5");
console.log(set.has(5));//true
set.delete(5);
console.log(set.has(5));//false
console.log(set.size);//1
set.claer();
console.log(set.has("5"));//false
console.log(set.size);//0
Set集合中的forEach()方法
forEach()方法的回調函數接受三個參數:
·Set集合中下一次索引的位置
·與第一個參數一樣的值
·被遍歷的Set集合本身
let set=new Set([1,2]);
set.forEach(function(value,key,ownerSet) {
console.log(key+" "+value);
console.log(ownerSet===set);
});
//1 1
//true
//2 2
true
令人疑惑的是Set的這個方法中前兩個參數都是一樣的,其實這也可以解釋得通,因為Set沒有鍵名,但是設為2個參數,就和Map集合和數組的forEach方法區別太大了,所以統一為三個參數。
let set=new Set([1,2]);
let processor={
output(value){
console.log(value);
},
process(dataSet){
dataSet.forEach(function(value){
this.output(value);
},this);
}
};
processor.process(set);
在forEach方法中,第二個參數也與數組的一樣,如果需要在回調中使用this調用,則可以將它作為第二個參數傳入forEach()函數;
//箭頭函數重寫
let set=new Set([1,2]);
let processor={
output(value){
console.log(value);
},
process(dataSet){
dataSet.forEach(value=>
this.output(value));
}
};
processor.process(set);
值得注意的是,盡管Set集合更適合用來追蹤多個值,而且又可以通過forEach()方法操作每個參數,但是你不能像訪問數組那樣直接通過索引訪問集合中的元素。如果有需要,可以轉化為一個數組。
將Set集合轉換為數組
數組轉換為Set集合很簡單,只要把數組傳入Set構造函數就可以了,轉換回去同樣很簡單,使用(...)運算符就可以了。
let set=new Set([1,2,3,3,3,4,5]),array=[...set];
console.log(array);//[1,2,3,4,5]
如果想要使數組變為無重復元素的數組,用這個方法就很簡單了。
let test=[1,2,2,3,3,6];
(function eliminateDuplicates(items){
return [...new Set(items)];
})(test)//[1,2,3,6]
Weak Set集合
將對象存儲在Set的實例與存儲在變量中完全一樣,只要Set實例中的引用存在,垃圾回收機制就不能釋放該對象的內存空間,于是之前提到的Set類型可以被看做是強引用的Set集合。
let set=new Set(),
key={};
set.add(key);
console.log(set.size);//1
//移除原始引用
key=null;
console.log(set.size);//1
key=[...set][0];
Set集合會保留原始引用,這容易導致內存泄漏,ES6中引入了另外的一個類型:WeakSet集合。
創建Weak Set集合
//Weak Set集合支持三個方法:add()、has()、delete()
let set=new WeakSet(),key={};
//向集合set中添加對象
set.add(key);
console.log(set.has(key));//true
set.delete(key);
console.log(set.has(key));//false
兩種類型的主要區別
let set=new WeakSet(),\
key={};
set.add(key);
console.log(set.has(key));//true
key=null;//移除了
因為has()方法要傳遞強用,所以接下來無法驗證了,但是JavaScript引擎會正確移除最后一個弱引用。
還有其它的差別:
1.在Weak Set的實例中,如果向add()、has()、delete()傳入非對象參數會導致程序報錯。
2.Weak Set集合不可迭代,不能用for-of循環。
3.Weak Set集合不暴露任何迭代器,所以無法通過程序自身來檢測其中內容。
4.Weak Set集合不支持forEach()方法。
5.Weak Set集合不支持size屬性。
ECMAScript中的Map集合
ECMAScript6中的Map類型是一種存儲著許多鍵值對的有序列表。其中鍵名和對應的值都支持所有的數據類型。鍵名的等價性判斷是通過調用Object.js()方法實現的。
如果需要添加新元素,可以使用set()方法,分別傳入鍵名和對應值作為兩個參數:如果要從集合中獲取信息,并調用get()方法。
let map=new Map();
map.set("title","Understanding ECMAScript");
map.set("year",2016);
console.log(map.get("title"));//"Understanding ECMAScript"
console.log(map.get("year"));//2016
console.log(map.get("CCG"));//undefined
可以使用對象作為對象屬性的鍵名。
let map=new Map(),
key1={},
key2={};
map.set(key1,5);
map.set(key2,42);
console.log(map.get(key1));//5
console.log(map.get(key2));//42
Map集合支持的方法
1.has(key)檢測鍵名是否存在。
2.delete(key)移除鍵名和對應值。
3.clear()移除Map集合中所有的鍵值對。
let map=new Map();
map.set("name","Nicholas");
map.set("age",25);
console.log(map.size);//2
console.log(map.has("name"));//true
console.log(map.get("name"));//"Nicholas"
console.log(map.has("age"));//true
console.log(map.get("age"));//25
map.delete("name");
console.log(map.has("name"));//false
console.log(map.get("name"));//undefined
console.log(map.size);//1
map.clear();
console.log(map.has("name"));//false
console.log(map.get("name"));//undefined
console.log(map.has("age"));//false
console.log(map.get("age"));//undefined
console.log(map.size);//0
Map集合初始化方法
可以向Map構造函數傳入數組來初始化一個Map集合,數組中的每個元素都是一個子數組,子數組包含一個鍵值對的鍵名與值兩個元素。
let map=new Map([["name","Nicholas"],["age",25]]);
console.log(map.has("name"));//true
console.log(map.get("name"));//"Nicholas"
console.log(map.has("age"));//true
console.log(map.get("age"));//25
console.log(map.size);//2
Map集合的forEach()方法
1.Map集合的下一次索引的位置
2.值對應的鍵名
3.Map集合本身
let map=new Map([["name","Nicholas"],["age",25]]);
map.forEach(function (value,key,ownerMap) {
console.log(key+" "+value);
console.log(ownerMap===Map);
});
//name Nicholas
//true
//age 25
//true
Weak Map集合
Weak Map是弱引用的Map集合,也用于存儲對象的弱引用。Weak Map集合中的鍵名必須是一個對象,如果使用非對象鍵名會報錯;集合中保存的是這些對象的弱引用,如果在弱引用之外不存在其他強引用,就會被自動回收,同事也會移除Weak Map集合的鍵值對。但是只有集合中的鍵名遵從這個規則,鍵名對應的值如果是一個對象,則保存對象的強引用,不會觸發垃圾收集。
Weak Map集合最大的用途就是保存Web頁面的DOM元素。
使用這個這種方法的困難是:一旦清楚元素,如何通過庫本身將對象清除。但是用Weak Map集合來跟蹤DOM元素,這些庫仍可以通過自定義對象整合,而且當DOM元素消失時,可以自動銷毀相關對象。
使用Weak Map集合(這部分引用阮一峰老師的博客:http://es6.ruanyifeng.com/#docs/set-map#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}
上面代碼中,鍵值obj是正常引用。所以,即使在 WeakMap 外部消除了obj的引用,WeakMap 內部的引用依然存在。
Weak Map集合支持的方法
1.has()檢測給定的鍵值在集合中是否存在。
2.delete()移除制定的兼職對。
3.set()寫入值。
4.get()得到值。
let map=new WeakMap(),
element=document.querySelector(".element");
map.set(element,"Original");
console.log(map.has(element));//true
console.log(map.get(element));//"Original"
map.delete(element);
console.log(map.has(element));//false
console.log(map.get(element));//undefined
Weak Map集合中的使用方法和使用限制
1.如果只用對象作為對象的鍵名,Weak Map是最好的選擇。
2.如果需要forEach()屬性,size屬性和clear()方法來管理集合中的元素,那么Map集合是一個更好的選擇,只是要注意內存的使用情況。