【JavaScript面试】Map和Set方法

Map

Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值。

声明一个空的Map

var map = new Map()

声明一个自定义的Map

var map = new Map([['name','Tom'],['age',18]])
注意:这里定义的[]中都是用,的形式存入

插入值到map

map.set('key','value')

根据键得到它对应的值

map.get('key')

判断map对应的键值是否存在

map.has('key')

删除map中某一对值

map.delete('key')

清空map

map.clear()
遍历整个map

浅析 Map 和 WeakMap 区别以及使用场景
以采用for…of循环和forEach两种方法。由于Map实例会维护键值对的插入顺序,因此可以根据插入顺序进行遍历

maps.forEach((elem,value) => {
  console.log(elem)     //这个是值
  console.log(value)    //这个是键
})

初始化Map需要一个二维数组,或者直接初始化一个空Map。Map具有以下方法:

var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined

由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:

var m = new Map();
m.set('Adam', 67);
m.set('Adam', 88);
m.get('Adam'); // 88 

Map的特点:

  1. Map 默认情况下不包含任何键,所有键都是自己添加进去的。不同于 Object 原型链上有一些默认的键。

  2. Map 的键可以是任意类型数据,就连函数都可以。

  3. Map 的键值对个数可以轻易通过size属性获取,Object 需要手动计算。

  4. Map 在频繁增删键值对的场景下性能要比 Object 好。

什么时候用 Map?

  • 要添加的键值名和 Object 上的默认键值名冲突,又不想改名时,用 Map
  • 需要 String 和 Symbol 以外的数据类型做键值时,用 Map
  • 键值对很多,有需要计算数量时,用 Map
  • 需要频繁增删键值对时,用 Map

WeakMap

WeakMap 对象的 key 只能是 Object 实例化的对象或者派生类的对象,如果不是的话,则会报错;WeakMap 对象的 value 可以是任意类型:

// 1. Object 实例化的对象
const k1 = new Object()

// 2. 使用字面量对象,字面量对象实际上也是 Object 实例化的对象
const k2 = {}

// 3. Object 派生类的对象
class Other extends Object{}
const k3 = new Other()

// WeakMap 的 value 可以是任意的基本类型或者引用对象
const wm = new WeakMap([
    [k1, 10086],
    [k2, "I am China Moblie"],
    [k3, new Array("Interesting")]
])

// 如果 key 不符合规范则会报错
const badWM = new WeakMap([
    [1, 2],
    ["Yoo", "Ugh"]
])
//> TypeError: Invalid value used as weak map key

weakMap 的其他 API 基本和 Map对象是一样的,但注意,并没有 size 属性和 clear() 方法

  • set(k, v),添加一对新的键值对,如果键已存在,则用新值覆盖其旧值
  • get(k),根据键得到它对应的值
  • has(k),判断是否存在该键
  • delete(k):删除指定的键值对
弱键

WeakMap 的 key 只能是 Object 实例化对象或者派生类对象的目的是,让这个key被弱持有。

假设我们有一个场景,我们需要存储DOM节点的属性以及它的值,我们可能会用到 Object 或者 Map,假设使用 Map:

const m = new Map()
// 假设我们需要保存一个登录按钮的属性值
const loginButton = document.querySelector("#login")
m.set(loginButton, {disabled: true})

这样会产生一个问题:当用户登录之后,跑到另外一个页面,登录按钮被移除了,正常来说,这个登录 DOM 节点应该也应该被垃圾回收器清除,但它被 loginButton 变量引用,而 loginButton 作为 key 被 map 引用,所以登录 DOM 节点会保留在内存中,白白占用空间。

这时候解决方法是手动解除引用,要么使用 delete 方法删除该键值对,要么等 Map 对象被销毁。

如果我们使用 WeakMap 对象进行同样的储存:

const wm = new WeakMap()
const loginButton = document.querySelector("#login")
wm.set(loginButton, {disabled: true})

作为 WeakMap 对象key的 loginButton 不会被算成正式的引用(formal reference),也就是说 loginButton 变量相当于不会被 wm 引用,这时垃圾回收器就可以把这个 loginButton 变量和登录 DOM节 点都给干掉,释放内存空间。

这样就起到了自动清理的效果,这也是 WeakMap 弱持有的目的所在。

不可迭代键

WeakMap 的 key 是不算正式引用,随时可能会被回收清除掉,因此 WeakMap 不提供迭代的功能。

对于 size 属性和 clear() 方法,由于它们需要先迭代遍历所有的 key 才能计算得到,所以同样无法使用。

Map和weakMap之间的区别和联系?

Map 相对于 WeakMap :

  • Map 的键可以是任意类型,WeakMap 只接受对象作为键(null除外),不接受其他类型的值作为键
  • Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键; WeakMap 的键是弱引用,键所指向的对象可以被垃圾回收,此时键是无效的
  • Map 可以被遍历, WeakMap 不能被遍历
WeakMap 的使用场景?
  1. DOM 节点元数据
  2. 部署私有属性
  3. 数据缓存

Set

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

NaN 和 undefined 都可以被存储在 Set 中,NaN 之间被视为相同的值(NaN 被认为是相同的,尽管 NaN !== NaN)

Set常用操作

方法/属性功能介绍
size获取当前Set对象的长度
add(value)向当前Set对象中添加一个值,返回的是Set对象,所以支持链式写法
delete(value)删除当前Set对象中的一个值,返回一个布尔值,表示是否删除成功
has(value)检测这个value是否是当前Set对象的一个元素,通过返回的布尔值表示
clear()清除当前Set对象所有元素,没有返回值
const s = new Set();
 
s.add(1).add(2).add(2);
 
console.log(s.size);
 
console.log(s.has(1));
console.log(s.has(2));
console.log(s.has(3));
 
s.delete(2);
console.log(s.has(2));
 
// set转数组
const items = new Set([1,2,3,4,5]);
const array = Array.from(items);
console.log(array);
 
// 去除数组重复成员
const items = [1,2,3,4,5];
console.log([...new Set(items]);

//注意:传入NaN和引用类型时,达不到去重的效果
 const arr = [1, {}, {}, 2]
 console.log([...new Set(arr)]); //[1, {}, {}, 2]
 
 const arr = [1, NaN, NaN, 2]
 console.log([...new Set(arr)]); // [1, NaN, 2]
let set = new Set(['red', 'green', 'blue']);
 
// 返回键名
for(let item of set.keys()) {
  console.log(item);
}
 
// 返回键值
for(let item of set.values()) {
  console.log(item);
}
// set 键名=键值
 
// 返回键值对
for(let item of set.entries()){
  console.log(item);
}
 
// 可以直接用 for of遍历Set
// for in 和 for of的区别是:in 是遍历对象,of是遍历值
for (let x of set) {
  console.log(x);
}
 
// set也有forEach()方法
set.forEach((value, key) => console.log(key + ' : ' + value));
// 此处forEach方法的参数是一个处理函数。
 
// 数组的 map 和 filter 方法也可以间接用于Set
let s = new Set([1,2,3]);
 
// map 将原数组映射成新数组
s = new Set([...s].map(x => x * 2));
console.log(s);
 
// filter返回过滤后的新数组
s = new Set([...s].filter(x => (x % 3) ==0));
console.log(s);
 
// 实现并集、交集、差集
let a = new Set([1,2,3]);
let b = new Set([4,3,2]);
 
let union = new Set([...a, ...b]);
console.log(union);
 
let intersect = new Set([...a].filter(x => b.has(x)));
console.log(intersect);
 
let difference = new Set([...a].filter(x => !b.has(x)));
console.log(difference);
 
// 在遍历操作中,同步改变原来的Set结构的两种变通方法
 
// 1.利用原Set结构映射出一个新的结构,然后赋值给原来的Set结构
let set1 = new Set([1,2,3]);
set1 = new Set([...set1].map(val => val *2));
console.log(set1);
 
// 2.利用Array.from
let set2 = new Set([1,2,3]);
set2 = new Set(Array.from(set2, val => val * 2));
console.log(set2);
const s = new Set();

[2,3,5,4,5,2,2].forEach(x => s.add(x));
// Set结构不会添加重复的值
 
for(let i of s) {
  console.log(i);
}
 
 
// ## 初始化
// 例一 可以接受一个数组作为参数
const set = new Set([1,2,3,4,4,]);
 
// ...将一个数组转为用逗号分隔的参数序列
console.log([...set]);
 
// 例二
const items = new Set([1,2,3,4,5,5,5,5,]);
console.log(items.size);
 
 
// 例三 可以接受具有iterable接口的其他数据结构作为参数
const set2 = new Set(document.querySelectorAll('div'));
console.log(set.size);
 
// 类似于
const set2 = new Set();
document
    .querySelectorAll('div')
    .forEach(div => set.add(div));
console.log(set.size);
 
// set中NaN等于自身,其余比较相当于 ===
let set3 = new Set();
let a = NaN;
let b = NaN;
set3.add(a);
set3.add(b);
console.log(set3)
 
// 两个对象总是不相等的
let set4 = new Set();
set4.add({});  // 1
console.log(set4.size);
 
set4.add({});  // 2
console.log(set4.size);

Set 遍历方法

方法/属性功能介绍
keys()返回该Set对象键名的遍历器,等同values()
values()返回该Set对象键值的遍历器,等同keys()
entries()返回该Set对象键值对的遍历器(目前感觉没什么用)
forEach()使用回调函数遍历该Set对象的每个元素
WeakSet

JavaScript垃圾回收是一种内存管理技术。在这种技术中,不再被引用的对象会被自动删除,而与其相关的资源也会被一同回收。Set中对象的引用都是强类型化的,并不会允许垃圾回收。这样一来,如果Set中引用了不再需要的大型对象,如已经从DOM树中删除的DOM元素,那么其回收代价是昂贵的。
为了解决这个问题,ES6还引入了WeakSet的弱集合。这些集合之所以是“弱的”,是因为它们允许从内存中清除不再需要的被这些集合所引用的对象。
首先让我们了解下WeakSet与Set的不同,以下三点是WeakSet与Set不一样的地方:

  1. Set可以存储值类型和对象引用类型,而WeakSet只能存储对象引用类型,否则会抛出TypeError。
  2. WeakSet不能包含无引用的对象,否则会被自动清除出集合(垃圾回收机制)。
  3. WeakSet对象是不可枚举的,也就是说无法获取大小,也无法获取其中包含的元素。
let weakset = new WeakSet();
(function(){ 
   let a = {}; 
   weakset.add(1); //TypeError: Invalid value used in weak set
   weakset.add(a);
})();  //here 'a' is garbage collected from weakset
console.log()
console.log(weakset.size); //output "undefined"
console.log(...weakset); //Exception is thrown
weakset.clear(); //Exception, no such function
const weakset=new WeakSet();
let foo={bar:1};
weakset.add(foo);
console.log(weakset.has(foo)); //output true
foo=null;
console.log(weakset.has(foo)); //output false

总结Map和Set的区别

(1) 这两种方法具有极快的查找速度;
(2) 初始化需要的值不一样,Map需要的是一个二维数组,而Set 需要的是一维 Array 数组
(3) Map 和 Set 都不允许键重复
(4) Map的键是不能修改,但是键对应的值是可以修改的;Set不能通过迭代器来改变Set的值,因为Set的值就是键。
(5) Map 是键值对的存在,值也不作为健;而 Set 没有 value 只有 key,value 就是 key;

10分钟讲明白WeakSet和WeakMap的弱引用

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵有千堆雪与长街

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值