文章目录
Map数据结构的定义
Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。
- 键的比较是基于 sameValueZero 算法:
- NaN 是与 NaN 相等的(虽然 NaN !等于 NaN),剩下所有其它的值是根据 === 运算符的结果判断是否相等。
- 在目前的ECMAScript规范中,-0和+0被认为是相等的,尽管这在早期的草案中并不是这样。有关详细信息,请参阅浏览器兼容性 表中的“Value equality for -0 and 0”。
Map和object的对比
Map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
也就是说,
Object 结构提供了“字符串—值”的对应,
Map 结构提供了“值—值”的对应是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
对比点 | Map | Oject |
---|---|---|
有无意外的键 | Map 默认情况不包含任何键。只包含显式插入的键。 | 一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突` |
键的类型 | 一个 Map的键可以是任意值,包括函数、对象或任意基本类型。 | 一个Object 的键必须是一个 String 或是Symbol。 |
键的顺序 | Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。 | 一个 Object 的键是无序的注意:自ECMAScript 2015规范以来,对象确实保留了字符串和Symbol键的创建顺序; 因此,在只有字符串键的对象上进行迭代将按插入顺序产生键。 |
size | Map 的键值对个数可以轻易地通过size 属性获取 | Object 的键值对个数只能手动计算 |
迭代 | Map 是 iterable 的,所以可以直接被迭代 | 迭代一个Object需要以某种方式获取它的键然后才能迭代。 |
性能 | 在频繁增删键值对的场景下表现更好。 | 在频繁添加和删除键值对的场景下未作出优化。 |
Map的深入理解
简单的方法set get has delete
const m = new Map()
const o = {x:'isX'}
m.set(o,'content')
console.log(m);
m.get(o)
console.log(m);
console.log(m.has(o));
m.delete(o)
console.log(m.has(o));
作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
const m = new Map([
['name','lisi'],
['age','18']
])
console.log(m.size);
console.log(m.has('name'));
console.log(m.get('name'));
任何具有 Iterator 接口、且每个成员都是一个双元素的数组( [[a,b],[c,d] ] )的数据结构都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
const s = new Set([
['name','lisi'],
['age','18']
])
const m = new Map(s)
console.log(m.get('age')); //18
const m2 = new Map([['x',1]])
const m3 = new Map(m2)
console.log(m3.get('x')); //1
如果对同一个键多次赋值,后面的值将覆盖前面的值。
如果读取一个未知的键,则返回undefined。
`注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。
const map = new Map()
map.set(['x'],1)
console.log(map.get(['x'])); //undefined
`
上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此get方法无法读取该键,返回undefined。
简而言之: Map数据结构的键名是内存中间的某一段数据, 至于这数据是啥无所谓, 只有同一位置(内存地址)的同一个数据才会能当做是同一个键名
如果 Map 的键是一个基础类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,
比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。
另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
Map数据结构的属性和方法
感觉基本上和set一样
size 属性
size属性返回 Map 结构的成员(一个键值对算一个)总数。
set(key, value)
set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
let map = new Map()
.set(1,'a')
.set(2,'b')
.set(3,'c')
set方法返回的是当前的Map对象,因此可以采用链式写法
get(key)
get方法读取key对应的键值,如果找不到key,返回undefined。
has(key)
has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
delete(key)
delete方法删除某个键,返回true。如果删除失败,返回false。
clear()
clear方法清除所有成员,没有返回值。
forEach
与数组的forEach方法类似,也可以实现遍历
遍历的方法
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。
forEach():遍历 Map 的所有成员。
Map 的遍历顺序就是插入顺序。
Map转化为数组结构(并用其遍历方法)
const m = new Map([['a',1],['b',2]])
// 转化为数组
console.log([...m]);
console.log([...m.keys()]);
console.log([...m.values()]);
console.log([...m.entries()]);
结合数组的map方法、filter方法,可以实现 Map 的遍历和过滤(Map 本身没有map和filter方法)。
let map = new Map()
.set(1,'a')
.set(2,'b')
.set(3,'c')
console.log(map);
const m = new Map([...map].map(([k,v])=>{
// console.log([k*2,v+'s']);
return [k*2,v+'s']
}))
console.log(m);//Map(3) {2 => "as", 4 => "bs", 6 => "cs"}
forEach方法还可以接受第二个参数,用来绑定this。
上面代码中,forEach方法的回调函数的this,就指向reporter。
Map和对象之间的转换
如果所有 Map 的键都是字符串,它可以无损地转为对象。如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。
function mapToObj(m){
let obj = {}
m.forEach((k,v)=> {
obj[k] = v
});
return obj
}
const m = new Map([['a',1],['b',2]])
console.log(mapToObj(m)); //{1: "a", 2: "b"}
对象转为Map
function objToMap(o){
let map = new Map()
for(let k of Object.keys(o)){
map.set(k,o[k])
}
return map
}
console.log(objToMap({a:1,b:2})); //Map(2) {"a" => 1, "b" => 2}
WeakMap数据类型及应用
WeakMap结构与Map结构类似,也是用于生成键值对的集合。
WeakMap与Map的区别有两点。
首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。
WeakMap的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。请看下面的例子
上面代码中,e1和e2是两个对象,我们通过arr数组对这两个对象添加一些文字说明。这就形成了arr对e1和e2的引用。
一旦不再需要这两个对象,我们就必须手动删除这个引用,否则垃圾回收机制就不会释放e1和e2占用的内存。
WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。
WeakMap结构有助于防止内存泄漏。
WeakMap只有四个方法可用:get()、set()、has()、delete()。
WeakMap的经典应用
myElement是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是myElement。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。
内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。