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