《红宝书》 |Map与WeakMap
Map
在ES6以前,JavaScript通过Object
来实现"键值"的储存,但这种方式有一定的问题。Map
是一种新的集合类型,也称映射。它带来了真正的"键值"储存机制。Map
的大多数特性可由Object
类型实现,但两者有细微的差距。
实例化
let m1=new Map()
//接收一个数组作为参数,该数组元素是表示键值对的数组
let m2=new Map([
["key1","value1"],
["key2","value2"],
["key3","value3"]
])
基本API
set(key,value)
:添加键/值对get(key)
:接受键名,获取对应值has(key)
:接受键名,判断是否存在该键/值对delete(key)
:接受键名,删除指定键/值对clear()
:清空实例中所有键/值对- 属性
size
:获取映射中键值对的数量const m=new Map() m.has("name") //false m.get("name") //undefined m.size //0 m.set("name","Matt") //添加键/值 m.has("name") //true m.get("name") //Matt m.size //1 m.delete("name") //删除键名为name的键/值对 m.clear() //清空m的键/值对
set()
会返回映射实例,因此可以把多个操作连起来const m=new Map() m.set("name","Matt") .set("age",16) //对同一个键多次赋值,后面的值将覆盖前面的值 m.set("name","Matt") .set("name","Amy")
映射特性
与Object只能使用数值、字符串或符号作为键不同,Map的键与值可以使用任何JavaScript类型作为键。
const m=new Map()
const functionKey=function(){}
const symbolKey=Symbol()
const objectKey=new Object()
m.set(functionKey,"functionValue")
m.set(symbolKey,"symbolValue")
m.set(objectKey,"objectValue")
只要内存地址不同,就视为两个键:
m.get(functionKey) //functionValue
m.get(function(){}) //undefined
当映射的键/值为引用类型时,若对其内容或属性修改,映射本身保持不变:
const m=new Map()
const objKey={}
const objValue={}
m.set(objKey,objValue)
//修改键/值对的内容或属性
objKey.name="Matt"
objValue.hobby="read"
//依然可以获取
m.get(objKey) //{hobby:read}
迭代应用
-
entries()
:返回键/值的迭代器。(可用Symbol.iterator属性代替) -
keys()
:返回键的迭代器 -
values()
:返回值的迭代器 -
forEach()
:遍历map的所有成员let m=new Map([ ["key1","value1"], ["key2","value2"], ["key3","value3"] ]) for(let item of m.entries()){ console.log(item) } //["key1","value1"] //["key2","value2"] //["key3","value3"] for(let item of m[Symbol.iterator]()){ console.log(item) } //["key1","value1"] //["key2","value2"] //["key3","value3"] for (let [key, value] of m) { console.log(key, value); } //key1,value1 //key2,value2 //key3,value3 for(let item of m.keys()){ console.log(item) } //key1 //key2 //key3 for(let item of m.values()){ console.log(item) } //value1 //value2 //value3 m.forEach((val,key)=>console.log(`${key}->${val}`)) // key1->value1 // key2->value2 // key3->value3
与Object的主要差异是,Map实例会维护键/值对的插入顺序。
转换类型
Map转为数组
通过扩展运算符(...
)或Array.from()
实现转换:
let map = new Map([
["key1","value1"],
["key2","value2"],
["key3","value3"]
]);
console.log([...map]) //[["key1","value1"],["key2","value2"],["key3","value3"]]
console.log([...map.entries()]) //[["key1","value1"],["key2","value2"],["key3","value3"]]
console.log([...map.keys()]) //["key1","key2","key3"]
console.log([...map.values()]) //["value1","value2","value3"]
console.log(Array.from(map)) //[["key1","value1"],["key2","value2"],["key3","value3"]]
结合数组的map
方法、filter
方法,可以实现Map的遍历和过滤:
let m1=new Map().set(1,"one").set(2,"two").set(3,"three")
let m2=new Map(
[...m1].filter(([key,value])=>key<3)
)
let m3=new Map(
[...m1].map(([key,value])=>[key+1,value+"*"])
)
console.log(m1) //Map(3) {1 => "one", 2 => "two", 3 => "three"}
console.log(m2) //Map(2) {1 => "one", 2 => "two"}
console.log(m3) //Map(3) {2 => "one*", 3 => "two*", 4 => "three*"}
Map转为对象
前提是键为字符串形式:
function mapToObj(map) {
let obj = Object.create(null);
for (let [k,v] of map) {
obj[k] = v;
}
return obj;
}
let m = new Map().set("name", "Matt").set("age", 16);
mapToObj(m) //{name: "Matt", age: 16}
对象转为Map
function objToMap(obj) {
let map = new Map();
for (let k of Object.keys(obj)) {
map.set(k, obj[k]);
}
return map;
}
objToMap({name: "Matt", age: 16}) //Map(2) {"name" => "Matt", "age" => 16}
选择Object还是Map
- 内存占用:储存单个键/值对所占用的内存数量会随着键的数量线性增加,批量添加或删除键/值对取决于个浏览器对该类型内存分配的工程。给定固定大小的内存,Map大约比Object多储存50%
- 插入性能:在所有浏览器中,向Map插入键/值对要比向Object插入稍微快一些。如果涉及大量插入操作,Map效果更佳
- 查找速度:Object效率更高
- 删除性能:如果涉及大量删除操作,Map的
delete()
效率更高
WeakMap
WeakMap称为弱映射。WeakMap是Map的兄弟类型,其API也是Map的子集。WeakMap中的weak描述的是JavaScript垃圾回收程序对待弱映射中的键的方式。
实例化
const wm=new WeakMap()
弱映射特性
弱映射中的键只能是Object或者是继承自Object的类型,否则会抛出TypeError错误。值的类型没有限制。
const key1={id:1},key2={id:2},key3={id:3}
const wm=new WeakMap([
[key1,"val1"],
[key2,"val2"],
[key3,"val3"]
])
初始化是全有全无的操作,只要有一个键不符合要求,那么就初始化失败:
const key1={id:1},key2={id:2},key3={id:3}
const wm=new WeakMap([
[key1,"val1"],
["key2","val2"],
[key3,"val3"]
])
原始值可先转换为原始包装类型再作为键:
const strKey=new String("key1")
const wm=new WeakMap([
[strKey,"val1"]
])
与Map相比,WeakMap是不可迭代的。
基本API
set(key,value)
:添加键/值对get(key)
:接受键名,获取对应值has(key)
:接受键名,判断是否存在该键/值对delete(key)
:接受键名,删除指定键/值对
与Map相比,少了
clear()
和size
属性。