Js大部分歷史時期都只存在一種集合類型,也就是數組類型。數組在 JS 中的使用正如其餘語言的數組同樣,但缺乏更多類型的集合致使數組也常常被看成隊列與棧來使用。數組只使用了數值型的索引,而若是非數值型的索引是必要的,開發者便會使用非數組的對象。這種技巧引出了非數組對象的定製實現,即 Set 與 Map 。算法
Set結構相似於數組,可是沒有重複結構
Set會自動移除重複的值,所以能夠用來過濾數組中的重複結構
Set內的對象是強引用json
a) let set = new Set([1, 2, 3, 4]);數組
Set 構造器實際上能夠接收任意可迭代對象做爲參數。能使用數組是由於它們默認就是可迭代的,Set與Map也是同樣,Set構造器會使用迭代器來提取參數中的值瀏覽器
b) let set = new Set();緩存
set.add(1);
set.add(2);
set.add(2);
//若是add對相同的值進行了屢次調用,那麼那麼在第一次以後的調用實際上會被忽略
set.add(3);
set.add('3');
// Set 不會使用強制類型轉換來判斷值是否重複。這意味着 Set 能夠同時包含數值 3 與 字符串 "3" ,將它們都做爲相對獨立的項.
Set判斷是否重複使用了Object.is() 方法,惟一的例外是 +0 與 -0 在 Set 中被判斷爲是相等的數據結構
c) 還能夠向Set添加多個值相同的對象,他們不會被合併爲同一項。函數
let set = new Set();
let key1 = {};
let key2 = {};
set.add(key1);
set.add(key2);優化
Set.prototype.constructor:構造函數,默認就是Set函數。
Set.prototype.size:返回Set實例的成員總數。this
Set的方法分爲兩類:操做方法 和 遍歷方法es5
• add(value):添加某個值,返回 Set 結構自己。
由於返回set自己,因此能夠寫成:
set.add(1).add(2).add(3)
• delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
set.delete(2) ; // true
• has(value):返回一個布爾值,表示該值是否爲Set的成員。
set.has(2) ; // false
• clear():清除全部成員,沒有返回值。
forEach():使用回調函數遍歷每一個成員
es5給數組添加了forEach()方法,使得更容易處理數組中的每一項,沒有返回值
對於數組來講forEach的三個參數:
arr[].forEach(function(value,index,array){//do})
value數組中的當前項, index當前項的索引, array原始數組
可是對於Set來講:
let set2 = new Set([{'a':1}, {'b':2}]);
set2.forEach(function(value, key, ownerSet) {
console.log(ownerSet === set2);
console.log(value === key);
});
key 與 value 老是相同的,同時 ownerSet 也始終等於 set 。此代碼輸出:
true
true
true
true
若是想在回調函數中使用當前的this,還能夠在forEach中傳入this做爲第二個參數
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);
本例中,在processor.process方法中的set調用了forEach方法,並傳遞了this做爲第二個參數,這樣便能正確的調用到this.output()方法
或者也可使用箭頭函數來達到相同的效果,無需傳入this,只需將上邊的process改寫成:
process(dataSet) {
dataSet.forEach((value) => this.output(value));
}
keys()、values()和entries()
keys():返回鍵名的遍歷器
values():返回鍵值的遍歷器
entries():返回鍵值對的遍歷器
keys方法、values方法、entries方法返回的都是遍歷器對象,因爲Set結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),因此keys方法和values方法的行爲徹底一致。
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
上面代碼中,entries方法返回的遍歷器,同時包括鍵名和鍵值,因此每次輸出一個數組,它的兩個成員徹底相等。
Set 結構的實例默承認遍歷,它的默認遍歷器生成函數就是它的values方法。
Set.prototype[Symbol.iterator] === Set.prototype.values
// true
這就意味着,能夠省略values方法,直接用for…of循環遍歷Set
let set = new Set(['red', 'green', 'blue']);
for (let x of set) {
console.log(x);
}
// red
// green
// blue
Set最經典的應用就是數組排重,
Set的對象經過指針指向的內存中的地址來作比較,因此要判斷對象是否相等,只能經過遍歷去判斷,這其中包含了不少邊際狀況。因此set沒有去判斷這些,只要往裏面扔一個對象,那麼就是一個新的元素
使用擴展運算符能夠很簡單的將Set轉成數組
let set = new Set([1, 2, 3, 3, 3, 4, 5]),
let array = [...set];
console.log(array); // [1,2,3,4,5]
該示例很好的說明了Set的一個重要的功能,數組排重,固然也很好的展現了,如何從Set轉成數組的過程。
注意,這種轉變是生成了一個新的數組對象原來的Set依然存在。
如最初的構造示例:
let set = new Set([1,4,5,6,7]);
主要寫自身特性和與Set的區別?
驗證:外部賦空看內部是否變化?
外部回收後?
因爲 Set 類型存儲對象引用的方式,它也能夠被稱爲 Strong Set 。對象存儲在 Set 的一個實例中時,實際上至關於把對象存儲在變量中。只要對於 Set 實例的引用仍然存在,所存儲的對象就沒法被垃圾回收機制回收,從而沒法釋放內存。
當 JS 代碼在網頁中運行,同時你想保持與 DOM 元素的聯繫,在該元素可能被其餘腳本移除的狀況下,你應當不但願本身的代碼保留對該 DOM 元素的最後一個引用(這種狀況被稱爲內存泄漏)
爲了緩解這個問題, ES6 也包含了 Weak Set ,該類型只容許存儲對象弱引用,而不能存儲基本類型的值。對象的弱引用在它本身成爲該對象的惟一引用時,不會阻止垃圾回收
WeakSet 結構有如下三個方法。
WeakSet.prototype.add(value):向 WeakSet 實例添加一個新成員。
WeakSet.prototype.delete(value):清除 WeakSet 實例的指定成員。
WeakSet.prototype.has(value):返回一個布爾值,表示某個值是否在 WeakSet 實例之中。
has() 或 delete() 則會在傳入了非對象的參數時返回 false );
程手段可用於判斷 Weak Set 的內容;
WeakSet之因此不可遍歷是因爲 WeakSet 內部有多少個成員,取決於垃圾回收機制有沒有運行,運行先後極可能成員個數是不同的,而垃圾回收機制什麼時候運行是不可預測的,所以 ES6 規定 WeakSet 不可遍歷。
WeakSet經常使用場景
Weak Set 看起來功能有限,而這對於正確管理內存而言是必要的。通常來講,若只想追蹤對象的引用,應當使用 Weak Set 而不是正規 Set
若是Set中引用了再也不須要的大型對象,如已經從DOM樹中刪除的DOM元素,那麼其回收代價是昂貴的
JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),可是傳統上只能用字符串看成鍵。這給它的使用帶來了很大的限制。
爲了解決這個問題,ES6 提供了 Map 數據結構。它相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,Object 結構提供了「字符串—值」的對應,Map 結構提供了「值—值」的對應,是一種更完善的 Hash 結構實現。若是你須要「鍵值對」的數據結構,Map 比 Object 更合適。
ES6 的 Map 類型是鍵值對的有序列表,而鍵和值均可以是任意類型。鍵的比較使用的是Object.is() ,所以你能將 5 與 "5" 同時做爲鍵,由於它們類型不一樣。這與使用對象屬性
做爲鍵的方式(指的是用對象來模擬 Map )大相徑庭,由於對象的屬性會被強制轉換爲字符串。
你能夠調用 set() 方法並給它傳遞一個鍵與一個關聯的值,來給 Map 添加項;此後使用鍵名來調用 get() 方法便能提取對應的值。例如:
let map = new Map();
map.set("title", "Understanding ES6");
map.set("year", 2016);
console.log(map.get("title")); // "Understanding ES6"
console.log(map.get("year")); // 2016
依然與 Set 相似,你能將數組傳遞給 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.size); // 2
經過構造器中的初始化, "name" 與 "age" 這兩個鍵就被添加到 map 變量中。雖然由數組構成的數組看起來有點奇怪,這對於準確表示鍵來講倒是必要的:由於鍵容許是任意數據類型,將鍵存儲在數組中,是確保它們在被添加到 Map 以前不會被強制轉換爲其餘類型的惟一方法。
Map構造函數接受數組做爲參數,實際上執行的是下面的算法。
const items = [
["name", "Nicholas"]
["age", 25] ]
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);
事實上,不只僅是數組,任何具備 Iterator 接口、且每一個成員都是一個雙元素的數組的數據結構均可以當作Map構造函數的參數。這就是說,Set和Map均可以用來生成新的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
上面代碼中,咱們分別使用 Set 對象和 Map 對象,看成Map構造函數的參數,結果都生成了新的 Map 對象。
Map 一樣擁有 size 屬性,用於指明包含了多少個鍵值對。如下代碼用不一樣方式使用了這三種方法以及 size 屬性:
set方法設置鍵名key對應的鍵值爲value,而後返回整個 Map 結構。若是key已經有值,則鍵值會被更新,不然就新生成該鍵。
const m = new Map();
m.set('edition', 6) // 鍵是字符串
m.set(262, 'standard') // 鍵是數值
m.set(undefined, 'nah') // 鍵是 undefined
set方法返回的是當前的Map對象,所以能夠採用鏈式寫法。
let map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
get方法讀取key對應的鍵值,若是找不到key,返回undefined。
const m = new Map();
const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 鍵是函數
m.get(hello) // Hello ES6!
Map 與 Set 共享了幾個方法,這是有意的,容許你使用類似的方式來與 Map 及 Set 進行交互。如下三個方法在 Map 與 Set 上都存在:
has方法返回一個布爾值,表示某個鍵是否在當前 Map 對象之中。
const m = new Map();
m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');
m.has('edition') // true
m.has('years') // false
m.has(262) // true
m.has(undefined) // true
delete方法刪除某個鍵,返回true。若是刪除失敗,返回false。
const m = new Map();
m.set(undefined, 'nah');
m.has(undefined) // true
m.delete(undefined)
m.has(undefined) // false
clear方法清除全部成員,沒有返回值。
let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.clear()
map.size // 0
• keys():返回鍵名的遍歷器。
• values():返回鍵值的遍歷器。
• entries():返回全部成員的遍歷器
須要特別注意的是,Map 的遍歷順序就是插入順序。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同於使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
上面代碼最後的那個例子,表示 Map 結構的默認遍歷器接口(Symbol.iterator屬性),就是entries方法。
Map 的 forEach() 方法相似於 Set 與數組的同名方法,它接受一個能接收三個參數的回調函數:
回調函數的這些參數更緊密契合了數組 forEach() 方法的行爲,即:第一個參數是值、第二個參數則是鍵(數組中的鍵是數值索引)。此處有個示例:
let map = new Map([
["name", "Nicholas"],
["age", 25]
]);
map.forEach(function(value, key, ownerMap) {
console.log(key + " " + value);
console.log(ownerMap === map);
});
forEach() 的回調函數輸出了傳給它的信息。其中 value 與 key 被直接輸出, ownerMap
與 map 進行了比較,說明它們是相等的。這就輸出了:
name Nicholas
true
age 25
true
Map 結構轉爲數組結構,比較快速的方法是使用擴展運算符(...)。
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
將數組傳入 Map 構造函數,就能夠轉爲 Map。
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
若是全部 Map 的鍵都是字符串,它能夠無損地轉爲對象。
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
若是有非字符串的鍵名,那麼這個鍵名會被轉成字符串,再做爲對象的鍵名。
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}
Map 轉爲 JSON 要區分兩種狀況。一種狀況是,Map 的鍵名都是字符串,這時能夠選擇轉爲對象 JSON。
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
另外一種狀況是,Map 的鍵名有非字符串,這時能夠選擇轉爲數組 JSON。
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
JSON 轉爲 Map,正常狀況下,全部鍵名都是字符串。
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
可是,有一種特殊狀況,整個 JSON 就是一個數組,且每一個數組成員自己,又是一個有兩個成員的數組。這時,它能夠一一對應地轉爲 Map。這每每是 Map 轉爲數組 JSON 的逆操做。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
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
其次,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 裏面的鍵名對象和所對應的鍵值對會自動消失,不用手動刪除引用。
ES6 的 WeakMap 類型是鍵值對的無序列表,其中鍵必須是非空的對象,值則容許是任意類型。 WeakMap 的接口與 Map 的很是類似
// 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的屬性和方法:
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
Weak Map 的最佳用武之地,就是在瀏覽器中建立一個關聯到特定 DOM 元素的對象。例如,某些用在網頁上的 JS 庫會維護一個自定義對象,用於引用該庫所使用的每個 DOM 元素,而且其映射關係會存儲在內部的對象緩存中。
該方法的困難之處在於:如何判斷一個 DOM 元素已不復存在於網頁中,以便該庫能移除此元素的關聯對象。若作不到,該庫就會繼續保持對 DOM 元素的一個無效引用,並形成內存泄漏。使用 Weak Map 來追蹤 DOM 元素,依然容許將自定義對象關聯到每一個 DOM 元素,而在此對象所關聯的 DOM 元素不復存在時,它就會在 Weak Map 中被自動銷燬。
必須注意的是, Weak Map 的鍵纔是弱引用,而值不是。在 Weak Map 的值中存儲對象會阻止垃圾回收,即便該對象的其餘引用已全都被移除。
當決定是要使用 Weak Map 仍是使用正規 Map 時,首要考慮因素在於你是否只想使用對象類型的鍵。若是你打算這麼作,那麼最好的選擇就是 Weak Map 。由於它能確保額外數據在再也不可用後被銷燬,從而能優化內存使用並規避內存泄漏。
要記住 Weak Map 只爲它們的內容提供了很小的可見度,所以你不能使用 forEach() 方法、size 屬性或 clear() 方法來管理其中的項。若是你確實須要一些檢測功能,那麼正規 Map會是更好的選擇,只是必定要確保留意內存的使用。
//map和set 比較的方法是否是同樣NaN 和 +0 -0