“睡服”面试官系列第七篇之map数据结构(建议收藏学习)
目錄
?
1map
1.1含義和基本用法
1.2實(shí)例的屬性和操作方法
1.2.1size屬性
1.2.2set(key, value)?
1.2.3get(key)
1.2.4has(key)
1.2.5delete(key)
1.2.6clear()
1.3遍歷方法
1.4與其他數(shù)據(jù)結(jié)構(gòu)的互相轉(zhuǎn)換
1.4.1Map 轉(zhuǎn)為數(shù)組
1.4.2數(shù)組 轉(zhuǎn)為 Map
1.4.3Map 轉(zhuǎn)為對(duì)象
1.4.4對(duì)象轉(zhuǎn)為 Map
1.4.5Map 轉(zhuǎn)為 JSON
1.4.6JSON 轉(zhuǎn)為 Map
2WeakMap
2.1含義
2.2WeakMap 的語(yǔ)法
2.3WeakMap 的示例
2.4WeakMap 的用途
總結(jié)
“睡服“面試官系列之各系列目錄匯總(建議學(xué)習(xí)收藏)
1map
1.1含義和基本用法
JavaScript 的對(duì)象(Object),本質(zhì)上是鍵值對(duì)的集合(Hash 結(jié)構(gòu)),但是傳統(tǒng)上只能用字符串當(dāng)作鍵。這給它的使用帶來(lái)了很大的限制
const data = {}; const element = document.getElementById('myDiv'); data[element] = 'metadata'; data['[object HTMLDivElement]'] // "metadata"上面代碼原意是將一個(gè) DOM 節(jié)點(diǎn)作為對(duì)象 data 的鍵,但是由于對(duì)象只接受字符串作為鍵名,所以 element 被自動(dòng)轉(zhuǎn)為字符串 [object
HTMLDivElement] 。
為了解決這個(gè)問(wèn)題,ES6 提供了 Map 數(shù)據(jù)結(jié)構(gòu)。它類(lèi)似于對(duì)象,也是鍵值對(duì)的集合,但是“鍵”的范圍不限于字符串,各種類(lèi)型的值(包括對(duì)象)都可以
當(dāng)作鍵。也就是說(shuō),Object 結(jié)構(gòu)提供了“字符串—值”的對(duì)應(yīng),Map 結(jié)構(gòu)提供了“值—值”的對(duì)應(yīng),是一種更完善的 Hash 結(jié)構(gòu)實(shí)現(xiàn)。如果你需要“鍵值
對(duì)”的數(shù)據(jù)結(jié)構(gòu),Map 比 Object 更合適。
上面代碼使用 Map 結(jié)構(gòu)的 set 方法,將對(duì)象 o 當(dāng)作 m 的一個(gè)鍵,然后又使用 get 方法讀取這個(gè)鍵,接著使用 delete 方法刪除了這個(gè)鍵。
上面的例子展示了如何向 Map 添加成員。作為構(gòu)造函數(shù),Map 也可以接受一個(gè)數(shù)組作為參數(shù)。該數(shù)組的成員是一個(gè)個(gè)表示鍵值對(duì)的數(shù)組
上面代碼在新建 Map 實(shí)例時(shí),就指定了兩個(gè)鍵 name 和 title 。
Map 構(gòu)造函數(shù)接受數(shù)組作為參數(shù),實(shí)際上執(zhí)行的是下面的算法。
事實(shí)上,不僅僅是數(shù)組,任何具有 Iterator 接口、且每個(gè)成員都是一個(gè)雙元素的數(shù)組的數(shù)據(jù)結(jié)構(gòu)(詳見(jiàn)《Iterator》一章)都可以當(dāng)作 Map 構(gòu)造函數(shù)的參
數(shù)。這就是說(shuō), Set 和 Map 都可以用來(lái)生成新的 Map。
上面代碼中,我們分別使用 Set 對(duì)象和 Map 對(duì)象,當(dāng)作 Map 構(gòu)造函數(shù)的參數(shù),結(jié)果都生成了新的 Map 對(duì)象。
如果對(duì)同一個(gè)鍵多次賦值,后面的值將覆蓋前面的值。
上面代碼對(duì)鍵 1 連續(xù)賦值兩次,后一次的值覆蓋前一次的值。
如果讀取一個(gè)未知的鍵,則返回 undefined 。
注意,只有對(duì)同一個(gè)對(duì)象的引用,Map 結(jié)構(gòu)才將其視為同一個(gè)鍵。這一點(diǎn)要非常小心。
const map = new Map(); map.set(['a'], 555); map.get(['a']) // undefined上面代碼的 set 和 get 方法,表面是針對(duì)同一個(gè)鍵,但實(shí)際上這是兩個(gè)值,內(nèi)存地址是不一樣的,因此 get 方法無(wú)法讀取該鍵,返回 undefined 。
同理,同樣的值的兩個(gè)實(shí)例,在 Map 結(jié)構(gòu)中被視為兩個(gè)鍵。
上面代碼中,變量 k1 和 k2 的值是一樣的,但是它們?cè)?Map 結(jié)構(gòu)中被視為兩個(gè)鍵。
由上可知,Map 的鍵實(shí)際上是跟內(nèi)存地址綁定的,只要內(nèi)存地址不一樣,就視為兩個(gè)鍵。這就解決了同名屬性碰撞(clash)的問(wèn)題,我們擴(kuò)展別人的庫(kù)的
時(shí)候,如果使用對(duì)象作為鍵名,就不用擔(dān)心自己的屬性與原作者的屬性同名。
如果 Map 的鍵是一個(gè)簡(jiǎn)單類(lèi)型的值(數(shù)字、字符串、布爾值),則只要兩個(gè)值嚴(yán)格相等,Map 將其視為一個(gè)鍵,比如 0 和 -0 就是一個(gè)鍵,布爾值 true
和字符串 true 則是兩個(gè)不同的鍵。另外, undefined 和 null 也是兩個(gè)不同的鍵。雖然 NaN 不嚴(yán)格相等于自身,但 Map 將其視為同一個(gè)鍵。
1.2實(shí)例的屬性和操作方法
Map 結(jié)構(gòu)的實(shí)例有以下屬性和操作方法。
1.2.1size屬性
size 屬性返回 Map 結(jié)構(gòu)的成員總數(shù)
const map = new Map(); map.set('foo', true); map.set('bar', false); map.size // 21.2.2set(key, value)?
set 方法設(shè)置鍵名 key 對(duì)應(yīng)的鍵值為 value ,然后返回整個(gè) Map 結(jié)構(gòu)。如果 key 已經(jīng)有值,則鍵值會(huì)被更新,否則就新生成該鍵
const m = new Map(); m.set('edition', 6) // 鍵是字符串 m.set(262, 'standard') // 鍵是數(shù)值 m.set(undefined, 'nah') // 鍵是 undefinedset 方法返回的是當(dāng)前的 Map 對(duì)象,因此可以采用鏈?zhǔn)綄?xiě)法。
let map = new Map() .set(1, 'a') .set(2, 'b') .set(3, 'c')1.2.3get(key)
get 方法讀取 key 對(duì)應(yīng)的鍵值,如果找不到 key ,返回 undefined?
const m = new Map(); const hello = function() {console.log('hello');}; m.set(hello, 'Hello ES6!') // 鍵是函數(shù) m.get(hello) // Hello ES6!1.2.4has(key)
has 方法返回一個(gè)布爾值,表示某個(gè)鍵是否在當(dāng)前 Map 對(duì)象之中。
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) // true1.2.5delete(key)
delete 方法刪除某個(gè)鍵,返回 true 。如果刪除失敗,返回 false 。
const m = new Map(); m.set(undefined, 'nah'); m.has(undefined) // true m.delete(undefined) m.has(undefined) // false1.2.6clear()
let map = new Map(); map.set('foo', true); map.set('bar', false); map.size // 2 map.clear() map.size // 01.3遍歷方法
Map 結(jié)構(gòu)原生提供三個(gè)遍歷器生成函數(shù)和一個(gè)遍歷方法。
keys() :返回鍵名的遍歷器。
values() :返回鍵值的遍歷器。
entries() :返回所有成員的遍歷器。
forEach() :遍歷 Map 的所有成員。
需要特別注意的是,Map 的遍歷順序就是插入順序
上面代碼最后的那個(gè)例子,表示 Map 結(jié)構(gòu)的默認(rèn)遍歷器接口( Symbol.iterator 屬性),就是 entries 方法。
map[Symbol.iterator] === map.entries // trueMap 結(jié)構(gòu)轉(zhuǎn)為數(shù)組結(jié)構(gòu),比較快速的方法是使用擴(kuò)展運(yùn)算符( ... )
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']]結(jié)合數(shù)組的 map 方法、 filter 方法,可以實(shí)現(xiàn) Map 的遍歷和過(guò)濾(Map 本身沒(méi)有 map 和 filter 方法)
const map0 = new Map() .set(1, 'a') .set(2, 'b') .set(3, 'c'); const map1 = new Map( [...map0].filter(([k, v]) => k < 3) ); // 產(chǎn)生 Map 結(jié)構(gòu) {1 => 'a', 2 => 'b'} const map2 = new Map( [...map0].map(([k, v]) => [k * 2, '_' + v]) ); // 產(chǎn)生 Map 結(jié)構(gòu) {2 => '_a', 4 => '_b', 6 => '_c'}此外,Map 還有一個(gè) forEach 方法,與數(shù)組的 forEach 方法類(lèi)似,也可以實(shí)現(xiàn)遍歷。
map.forEach(function(value, key, map) { console.log("Key: %s, Value: %s", key, value); });forEach 方法還可以接受第二個(gè)參數(shù),用來(lái)綁定 this?
const reporter = { report: function(key, value) { console.log("Key: %s, Value: %s", key, value); } }; map.forEach(function(value, key, map) { this.report(key, value); }, reporter);上面代碼中, forEach 方法的回調(diào)函數(shù)的 this ,就指向 reporter 。
1.4與其他數(shù)據(jù)結(jié)構(gòu)的互相轉(zhuǎn)換
1.4.1Map 轉(zhuǎn)為數(shù)組
前面已經(jīng)提過(guò),Map 轉(zhuǎn)為數(shù)組最方便的方法,就是使用擴(kuò)展運(yùn)算符( ... )。
const myMap = new Map() .set(true, 7) .set({foo: 3}, ['abc']); [...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]1.4.2數(shù)組 轉(zhuǎn)為 Map
將數(shù)組傳入 Map 構(gòu)造函數(shù),就可以轉(zhuǎn)為 Map。
new Map([ [true, 7], [{foo: 3}, ['abc']] ]) // Map { // true => 7, // Object {foo: 3} => ['abc'] // }1.4.3Map 轉(zhuǎn)為對(duì)象
如果所有 Map 的鍵都是字符串,它可以轉(zhuǎn)為對(duì)象。
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 }1.4.4對(duì)象轉(zhuǎn)為 Map
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}1.4.5Map 轉(zhuǎn)為 JSON
Map 轉(zhuǎn)為 JSON 要區(qū)分兩種情況。一種情況是,Map 的鍵名都是字符串,這時(shí)可以選擇轉(zhuǎn)為對(duì)象 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 的鍵名有非字符串,這時(shí)可以選擇轉(zhuǎn)為數(shù)組 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"]]]1.4.6JSON 轉(zhuǎn)為 Map
JSON 轉(zhuǎn)為 Map,正常情況下,所有鍵名都是字符串。
function jsonToStrMap(jsonStr) { return objToStrMap(JSON.parse(jsonStr)); } jsonToStrMap('{"yes": true, "no": false}') // Map {'yes' => true, 'no' => false}但是,有一種特殊情況,整個(gè) JSON 就是一個(gè)數(shù)組,且每個(gè)數(shù)組成員本身,又是一個(gè)有兩個(gè)成員的數(shù)組。這時(shí),它可以一一對(duì)應(yīng)地轉(zhuǎn)為 Map。這往往是數(shù)
組轉(zhuǎn)為 JSON 的逆操作
2WeakMap
2.1含義
WeakMap 結(jié)構(gòu)與 Map 結(jié)構(gòu)類(lèi)似,也是用于生成鍵值對(duì)的集合
// WeakMap 可以使用 set 方法添加成員 const wm1 = new WeakMap(); const key = {foo: 1}; wm1.set(key, 2); wm1.get(key) // 2 // WeakMap 也可以接受一個(gè)數(shù)組, // 作為構(gòu)造函數(shù)的參數(shù) const k1 = [1, 2, 3]; const k2 = [4, 5, 6]; const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]); wm2.get(k2) // "bar"WeakMap 與 Map 的區(qū)別有兩點(diǎn)。
首先, WeakMap 只接受對(duì)象作為鍵名( null 除外),不接受其他類(lèi)型的值作為鍵名。
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上面代碼中,如果將數(shù)值 1 和 Symbol 值作為 WeakMap 的鍵名,都會(huì)報(bào)錯(cuò)。
其次, WeakMap 的鍵名所指向的對(duì)象,不計(jì)入垃圾回收機(jī)制。
WeakMap 的設(shè)計(jì)目的在于,有時(shí)我們想在某個(gè)對(duì)象上面存放一些數(shù)據(jù),但是這會(huì)形成對(duì)于這個(gè)對(duì)象的引用。請(qǐng)看下面的例子。
?上面代碼中, e1 和 e2 是兩個(gè)對(duì)象,我們通過(guò) arr 數(shù)組對(duì)這兩個(gè)對(duì)象添加一些文字說(shuō)明。這就形成了 arr 對(duì) e1 和 e2 的引用。
一旦不再需要這兩個(gè)對(duì)象,我們就必須手動(dòng)刪除這個(gè)引用,否則垃圾回收機(jī)制就不會(huì)釋放 e1 和 e2 占用的內(nèi)存。
上面這樣的寫(xiě)法顯然很不方便。一旦忘了寫(xiě),就會(huì)造成內(nèi)存泄露。
WeakMap 就是為了解決這個(gè)問(wèn)題而誕生的,它的鍵名所引用的對(duì)象都是弱引用,即垃圾回收機(jī)制不將該引用考慮在內(nèi)。因此,只要所引用的對(duì)象的其他
引用都被清除,垃圾回收機(jī)制就會(huì)釋放該對(duì)象所占用的內(nèi)存。也就是說(shuō),一旦不再需要,WeakMap 里面的鍵名對(duì)象和所對(duì)應(yīng)的鍵值對(duì)會(huì)自動(dòng)消失,不用
手動(dòng)刪除引用。
基本上,如果你要往對(duì)象上添加數(shù)據(jù),又不想干擾垃圾回收機(jī)制,就可以使用 WeakMap。一個(gè)典型應(yīng)用場(chǎng)景是,在網(wǎng)頁(yè)的 DOM 元素上添加數(shù)據(jù),就可
以使用 WeakMap 結(jié)構(gòu)。當(dāng)該 DOM 元素被清除,其所對(duì)應(yīng)的 WeakMap 記錄就會(huì)自動(dòng)被移除。
上面代碼中,先新建一個(gè) Weakmap 實(shí)例。然后,將一個(gè) DOM 節(jié)點(diǎn)作為鍵名存入該實(shí)例,并將一些附加信息作為鍵值,一起存放在 WeakMap 里面。
這時(shí),WeakMap 里面對(duì) element 的引用就是弱引用,不會(huì)被計(jì)入垃圾回收機(jī)制。
也就是說(shuō),上面的 DOM 節(jié)點(diǎn)對(duì)象的引用計(jì)數(shù)是 1 ,而不是 2 。這時(shí),一旦消除對(duì)該節(jié)點(diǎn)的引用,它占用的內(nèi)存就會(huì)被垃圾回收機(jī)制釋放。Weakmap 保
存的這個(gè)鍵值對(duì),也會(huì)自動(dòng)消失。
總之, WeakMap 的專(zhuān)用場(chǎng)合就是,它的鍵所對(duì)應(yīng)的對(duì)象,可能會(huì)在將來(lái)消失。 WeakMap 結(jié)構(gòu)有助于防止內(nèi)存泄漏。
注意,WeakMap 弱引用的只是鍵名,而不是鍵值。鍵值依然是正常引用。
上面代碼中,鍵值 obj 是正常引用。所以,即使在 WeakMap 外部消除了 obj 的引用,WeakMap 內(nèi)部的引用依然存在
2.2WeakMap 的語(yǔ)法
WeakMap 與 Map 在 API 上的區(qū)別主要是兩個(gè),一是沒(méi)有遍歷操作(即沒(méi)有 key() 、 values() 和 entries() 方法),也沒(méi)有 size 屬性。因?yàn)闆](méi)有辦法
列出所有鍵名,某個(gè)鍵名是否存在完全不可預(yù)測(cè),跟垃圾回收機(jī)制是否運(yùn)行相關(guān)。這一刻可以取到鍵名,下一刻垃圾回收機(jī)制突然運(yùn)行了,這個(gè)鍵名就沒(méi)
了,為了防止出現(xiàn)不確定性,就統(tǒng)一規(guī)定不能取到鍵名。二是無(wú)法清空,即不支持 clear 方法。因此, WeakMap 只有四個(gè)方法可用: get() 、 set() 、
has() 、 delete()?
2.3WeakMap 的示例
WeakMap 的例子很難演示,因?yàn)闊o(wú)法觀察它里面的引用會(huì)自動(dòng)消失。此時(shí),其他引用都解除了,已經(jīng)沒(méi)有引用指向 WeakMap 的鍵名了,導(dǎo)致無(wú)法證實(shí)
那個(gè)鍵名是不是存在。
賀師俊老師提示,如果引用所指向的值占用特別多的內(nèi)存,就可以通過(guò) Node 的 process.memoryUsage 方法看出來(lái)。根據(jù)這個(gè)思路,網(wǎng)友vtxf補(bǔ)充了下面
的例子。
首先,打開(kāi) Node 命令行。
上面代碼中, --expose-gc 參數(shù)表示允許手動(dòng)執(zhí)行垃圾回收機(jī)制。
然后,執(zhí)行下面的代碼
上面代碼中,只要外部的引用消失,WeakMap 內(nèi)部的引用,就會(huì)自動(dòng)被垃圾回收清除。由此可見(jiàn),有了 WeakMap 的幫助,解決內(nèi)存泄漏就會(huì)簡(jiǎn)單很
多。
2.4WeakMap 的用途
前文說(shuō)過(guò),WeakMap 應(yīng)用的典型場(chǎng)合就是 DOM 節(jié)點(diǎn)作為鍵名。下面是一個(gè)例子。
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 是一個(gè) DOM 節(jié)點(diǎn),每當(dāng)發(fā)生 click 事件,就更新一下?tīng)顟B(tài)。我們將這個(gè)狀態(tài)作為鍵值放在 WeakMap 里,對(duì)應(yīng)的鍵名就是
myElement 。一旦這個(gè) DOM 節(jié)點(diǎn)刪除,該狀態(tài)就會(huì)自動(dòng)消失,不存在內(nèi)存泄漏風(fēng)險(xiǎn)。
WeakMap 的另一個(gè)用處是部署私有屬性
上面代碼中, Countdown 類(lèi)的兩個(gè)內(nèi)部屬性 _counter 和 _action ,是實(shí)例的弱引用,所以如果刪除實(shí)例,它們也就隨之消失,不會(huì)造成內(nèi)存泄漏
總結(jié)
本博客源于本人閱讀相關(guān)書(shū)籍和視頻總結(jié),創(chuàng)作不易,謝謝點(diǎn)贊支持。學(xué)到就是賺到。我是歌謠,勵(lì)志成為一名優(yōu)秀的技術(shù)革新人員。
歡迎私信交流,一起學(xué)習(xí),一起成長(zhǎng)。
推薦鏈接 其他文件目錄參照
“睡服“面試官系列之各系列目錄匯總(建議學(xué)習(xí)收藏)
總結(jié)
以上是生活随笔為你收集整理的“睡服”面试官系列第七篇之map数据结构(建议收藏学习)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。