介绍
Map 和 Object数据项集合的区别和使用
# Map的食用方法
- Map (opens new window) 是一个带键的数据项的集合,就像一个
Object
一样。 但是它们最大的差别是Map
允许任何类型的键(key)。甚至是正则类型作为key Map
是iterable类型 (opens new window) 可迭代类型, 可以通过for..of (opens new window)进行迭代操作
它的方法和属性如下:
new Map([iterable])
—— 创建 map。map.set(key, value)
—— 根据键存储值。map.get(key)
—— 根据键来返回值,如果map
中不存在对应的key
,则返回undefined
。map.has(key)
—— 如果key
存在则返回true
,否则返回false
。map.delete(key)
—— 删除指定键的值。map.clear()
—— 清空 map。map.size
—— 返回当前元素个数。可以获取Map
集合的长度(相当于length
)Object
中 只能通过Object.key()
或者for in
循环得到对象的长度
# 构造Map
- 通过
new Map()
创建 map。可以设置其默认值 Map
集合不会进行自动排序 他是依赖于你添加时候的顺序 不会自动帮你进行排序 (Object
会依据数字进行排序 且字符串会被转换成数字)
const map = new Map()
const mapD = new Map([
['a', 'b'],
['c', 'd'],
])
// 默认为空
console.log(map) // Map(0) {size: 0}
console.log(mapD) // Map(2) {'a' => 'b', 'c' => 'd'}
Map
的键可以是任意类型 包括对象, 数组, 函数等 他不会像Object
一样帮你隐式转换类型 在添加键值对的时候 会通过严格的全等===
来判断key
是否已经存在 如果key
全等 那么会被后添加的覆盖
NaN
的特殊情况Map
的键会进行严格的全等 如果两个key
全等那么会被覆盖NaN === NaN
是false
但是NaN
是特例 在Map
中他会被当成全等true
所以会被覆盖
# Map键值对的访问
- 先记录一下Object (opens new window) 一些访问方法
// 设置一个空对象
const ret = {}
// 赋值一个普通key
ret.hobby = '敲代码'
// 赋值一个静态key
ret[Symbol('name')] = '你好'
// 判断key是否存在 或 是否可访问
console.log(ret.hobby === undefined) // false false为纯在 他true为不存在
console.log(ret.name === undefined) // true 静态属性无法访问
// 删除普通key
delete ret.hobby
console.log(ret) // {Symbol(name): '你好'}
// 删除静态key 静态属性不能被删除哦
delete ret.name
console.log(ret) // {Symbol(name): '你好'}
Map
需要一些自身方法 才可以取到值 或者修改 删除值- Symbol (opens new window) 静态属性只会被
clear()
清空 其他的Map
方法无法访问和修改
- Symbol (opens new window) 静态属性只会被
// 声明一个map
const map = new Map()
// 设置一个key
map.set('name', '小王')
// 设置一个静态key
map.set(Symbol('id'), 12306)
console.log(map) // Map(2) {'name' => '小王'} map默认会隐藏静态属性
// 修改指定的key-value值
map.set('name', '小刘')
// 修改静态属性的key-value值 静态属性不会被覆盖 会单独进行普通值的创建 所以重名也没关系
map.set('id', 333)
// 判断属性是否存在
map.has('name') // true true为存在false为不存在
// 静态属性通过has判断是否 不存在为false
map.has('id') // false
// 取值操作
map.get('name') // 小刘
// 静态属性无法取到值
map.get('id') // undefined
// 获取所有的map中所有的属性名 静态属性无法获取
map.keys() // MapIterator {'name'}
// 删除操作
map.delete('name')
// 静态属性无法进行删除
map.delete('name')
// 清空map 所有包含在map中的key-value 都会清除 包括静态属性
map.clear() // Map(0) {size: 0}
# Map中声明函数方法
Map
中也可以声明方法 并且支持方法, 通过map.set()
读取方法来使用
// 声明一个map
const map = new Map([['name', '小刘']])
// 把函数方法塞进map中
map.set('fn', () => {
return `大家好啊我是${map.get('name')}`
})
// 获取map方法
const mapFn = map.get('fn')
console.log(mapFn()) // 大家好啊我是小刘
# Map中声明函数方法并传参
Map
声明函数方法还可以进行传参, 通过map.set()
读取方方法后进行传参, 使用的方式和声明一个普通函数一样
// 声明一个map
const mapFunction = new Map([['乘法传参', (number) => number * 2]]) // value是一个方法
// 读取map方法并进行传参
const ret = mapFunction.get('乘法传参')(2)
console.log(ret) // 4
Map
方法中还可以使用可选链运算符?.
, 来判断Map集合中是否包含该函数方法, 这一点在ts中非常重要
ts出现以上这种Map函数方法传参报错, 就可以通过?.
来解决
// 解决ts中未定义对象的报错
this.mapFunction.get('高光金属')?.(child)
// 接着上面的例子
// 声明一个map
const mapFunction = new Map([['乘法传参', (number) => number * 2]])
// 读取map方法并进行传参
const ret = mapFunction.get('乘法传参')?.(2) // 设置可选链运算符防止未定义对象
console.log(ret) // 4
# Map 迭代/循环 (opens new window)
map提供了iterator (opens new window) 实例可以进行迭代处理 通过for...of (opens new window) 进行迭代
如果要在
Map
里使用循环,可以使用以下三个方法:map.keys()
—— 遍历并返回一个包含所有键的可迭代对象,map.values()
—— 遍历并返回一个包含所有值的可迭代对象,map.entries()
—— 遍历并返回一个包含所有实体[key, value]
的可迭代对象,for..of
在默认情况下使用的就是这个。
进行Map迭代案例
// 遍历所有的键(key)
for (let vegetable of recipeMap.keys()) {
console.log(vegetable); // cucumber, tomatoes, onion
}
// 遍历所有的值(value)
for (let amount of recipeMap.values()) {
console.log(amount); // 500, 350, 50
}
// 遍历所有的实体 [key, value]
for (let entry of recipeMap) { // 与 recipeMap.entries() 相同
console.log(entry); // ['cucumber', 500] ['tomatoes', 350] ['onion', 50]
}
// 遍历所有的实体 [key, value]
for (let amount of recipeMap.entries()) {
console.log(amount); //['cucumber', 500] ['tomatoes', 350] ['onion', 50]
}
迭代的顺序与插入值的顺序相同。与普通的
Object
不同,Map
保留了此顺序。Object
会帮你按照数字进行排序遍历除此之外,
Map
有内建的 forEach (opens new window)方法,与Array
类似:
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// 对每个键值对 (key, value) 运行 forEach 函数
recipeMap.forEach((value, key, map) => {
console.log(`${key}: ${value}`);
})
// cucumber: 500
// tomatoes: 350
// onion: 50
# Map JSON序列化
Map
不能直接使用JSON.stringify() (opens new window)序列化 但是Object
可以直接使用JSON.stringify() (opens new window)序列化
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
])
// map使用JSON序列化
const ret = JSON.stringify(recipeMap)
// map无法直接使用JSON序列化
console.log(ret) // {}
- 如果非要使用 可以先把
map
转换成Array.from() (opens new window) 数组解构 然后再使用JSON.stringify() (opens new window)序列化
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
])
// map使用JSON序列化 先使用Array.from把map转换为数组 然后再JSON.stringify给其转换成JSON格式
const ret = JSON.stringify(Array.from(recipeMap))
console.log(ret) // [["cucumber",500],["tomatoes",350],["onion",50]]
Map
不适合给后端传递JSON
格式 建议使用Object
格式
# 适用场景
- 可以看到
map
和Object
格式差不多 使用场景怎么合适呢
# Object.entries:从对象创建 Map (opens new window)
当创建一个 Map
后,我们可以传入一个带有键值对的数组(或其它可迭代对象)来进行初始化,如下所示:
// 键值对 [key, value] 数组
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
alert( map.get('1') ); // str1
如果我们想从一个已有的普通对象(plain object)来创建一个 Map
,那么我们可以使用内建方法 Object.entries(obj) (opens new window),该方法返回对象的键/值对数组,该数组格式完全按照 Map
所需的格式。
所以可以像下面这样从一个对象创建一个 Map:
let obj = {
name: "John",
age: 30
};
let map = new Map(Object.entries(obj));
alert( map.get('name') ); // John
这里,Object.entries
返回键/值对数组:[ ["name","John"], ["age", 30] ]
。这就是 Map
所需要的格式。
# Object.fromEntries:从 Map 创建对象 (opens new window)
我们刚刚已经学习了如何使用 Object.entries(obj)
从普通对象(plain object)创建 Map
。
Object.fromEntries
方法的作用是相反的:给定一个具有 [key, value]
键值对的数组,它会根据给定数组创建一个对象:
let prices = Object.fromEntries([
['banana', 1],
['orange', 2],
['meat', 4]
]);
// 现在 prices = { banana: 1, orange: 2, meat: 4 }
alert(prices.orange); // 2
我们可以使用 Object.fromEntries
从 Map
得到一个普通对象(plain object)。
例如,我们在 Map
中存储了一些数据,但是我们需要把这些数据传给需要普通对象(plain object)的第三方代码。
我们来开始:
let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);
let obj = Object.fromEntries(map.entries()); // 创建一个普通对象(plain object)(*)
// 完成了!
// obj = { banana: 1, orange: 2, meat: 4 }
alert(obj.orange); // 2
调用 map.entries()
将返回一个可迭代的键/值对,这刚好是 Object.fromEntries
所需要的格式。
我们可以把带 (*)
这一行写得更短:
let obj = Object.fromEntries(map); // 省掉 .entries()
上面的代码作用也是一样的,因为 Object.fromEntries
期望得到一个可迭代对象作为参数,而不一定是数组。并且 map
的标准迭代会返回跟 map.entries()
一样的键/值对。因此,我们可以获得一个普通对象(plain object),其键/值对与 map
相同。
# 参考文献
JS每日一题:关于Object和Map的区别,你所不知道的细节 (opens new window)