目录
[2.1 是什么](#2.1 是什么)
[2.2 与普通对象的区别](#2.2 与普通对象的区别)
[2.3 常用方法](#2.3 常用方法)
[2.4 遍历方式](#2.4 遍历方式)
[2.5 使用场景](#2.5 使用场景)
[3.1 是什么](#3.1 是什么)
[3.2 常用方法](#3.2 常用方法)
[3.3 遍历方式](#3.3 遍历方式)
[3.4 使用场景](#3.4 使用场景)
[四、WeakMap(弱引用 Map)](#四、WeakMap(弱引用 Map))
[4.1 是什么](#4.1 是什么)
[4.2 核心特点](#4.2 核心特点)
[4.3 常用方法](#4.3 常用方法)
[4.4 与 Map 的区别](#4.4 与 Map 的区别)
[4.5 使用场景](#4.5 使用场景)
[五、WeakSet(弱引用 Set)](#五、WeakSet(弱引用 Set))
[5.1 是什么](#5.1 是什么)
[5.2 核心特点](#5.2 核心特点)
[5.3 常用方法](#5.3 常用方法)
[5.4 使用场景](#5.4 使用场景)
[Q1:Map 和 Object 的区别?](#Q1:Map 和 Object 的区别?)
[Q2:Set 如何实现数组去重?](#Q2:Set 如何实现数组去重?)
[Q3:WeakMap 和 Map 的区别?什么时候用 WeakMap?](#Q3:WeakMap 和 Map 的区别?什么时候用 WeakMap?)
[Q4:为什么 WeakMap 不可遍历?](#Q4:为什么 WeakMap 不可遍历?)
[Q5:[...arr] 和 arr 有什么区别?](#Q5:[...arr] 和 arr 有什么区别?)
[Q2:展开运算符和 Object.assign() 有什么区别?](#Q2:展开运算符和 Object.assign() 有什么区别?)
[Q3:... 和 rest 参数有什么区别?](#Q3:... 和 rest 参数有什么区别?)
一、核心区别速览
| 对比 | Map | Set | WeakMap | WeakSet |
|---|---|---|---|---|
| 存储内容 | 键值对 | 不重复的值 | 键值对(键只能是对象) | 不重复的对象 |
| 键的类型 | 任意类型 | 只有值(无键) | 只能是对象 | 只能是对象 |
| 是否可遍历 | ✅ 是 | ✅ 是 | ❌ 否 | ❌ 否 |
| 有 size 属性 | ✅ 有 | ✅ 有 | ❌ 无 | ❌ 无 |
| 弱引用 | ❌ 否 | ❌ 否 | ✅ 是 | ✅ 是 |
| GC 不影响 | ❌ 会阻止回收 | ❌ 会阻止回收 | ✅ 不阻止回收 | ✅ 不阻止回收 |
二、Map(键值对集合)
2.1 是什么
键值对集合,可以理解为"更强大的对象"。
2.2 与普通对象的区别
| 对比 | Object | Map |
|---|---|---|
| 键的类型 | 只能是 String / Symbol | 任意类型(对象、数组、函数、NaN) |
| 顺序 | 不严格保证 | 保留插入顺序 |
| 长度 | Object.keys(obj).length |
map.size |
| 遍历 | for...in + hasOwnProperty |
直接 for...of |
| 性能 | 一般 | 频繁增删时更好 |
2.3 常用方法
javascript
const map = new Map()
// 设置
map.set('name', '张三')
map.set(123, '数字键')
map.set({ id: 1 }, '对象键')
// 获取
map.get('name') // '张三'
map.get(123) // '数字键'
// 判断
map.has('name') // true
// 删除
map.delete('name') // true
// 长度
map.size // 2
// 清空
map.clear()
2.4 遍历方式
javascript
const map = new Map([
['name', '张三'],
['age', 18]
])
// for...of
for (let [key, value] of map) {
console.log(key, value)
}
// forEach
map.forEach((value, key) => {
console.log(key, value)
})
// 只遍历键
for (let key of map.keys()) { }
// 只遍历值
for (let value of map.values()) { }
2.5 使用场景
| 场景 | 说明 |
|---|---|
| 需要非字符串作为键 | 用对象作为键 |
| 频繁增删键值对 | Map 性能更好 |
| 需要有序遍历 | Map 保留插入顺序 |
三、Set(不重复值的集合)
3.1 是什么
一组不重复的值的集合,只有值,没有键。
3.2 常用方法
javascript
const set = new Set()
// 添加(重复添加无效)
set.add(1)
set.add(2)
set.add(2) // 无效,不会重复
set.add(3)
// 判断
set.has(2) // true
// 删除
set.delete(2)
// 长度
set.size // 2
// 清空
set.clear()
3.3 遍历方式
javascript
const set = new Set([1, 2, 3])
// for...of
for (let value of set) {
console.log(value)
}
// forEach
set.forEach(value => {
console.log(value)
})
3.4 使用场景
| 场景 | 代码 |
|---|---|
| 数组去重 | [...new Set(arr)] |
| 判断值是否存在 | set.has(value) |
| 存储唯一 ID | 防止重复添加 |
javascript
// 数组去重(最经典)
const arr = [1, 2, 2, 3, 3, 3]
const unique = [...new Set(arr)] // [1, 2, 3]
// 字符串去重
const str = 'hello'
const uniqueStr = [...new Set(str)].join('') // 'helo'
Set可以接收一个可迭代对象(数组、字符串等)
-
字符串
'hello'是一个可迭代对象,会按字符迭代 -
Set自动去重,所以'l'只保留一个,得到{'h', 'e', 'l', 'o'} -
...可以把一个"可迭代对象"(数组、字符串、Set、Map 等)拆成一个个独立的值。 -
Set 是有序的可迭代对象,按插入顺序展开
-
得到一个新数组
['h', 'e', 'l', 'o']
-
join('')把数组元素用空字符串连接 -
['h', 'e', 'l', 'o']→'helo'
四、WeakMap(弱引用 Map)
4.1 是什么
弱引用版本的 Map,键必须是对象,不会阻止垃圾回收。
4.2 核心特点
| 特点 | 说明 |
|---|---|
| 键只能是对象 | 不能用基本类型作为键 |
| 弱引用 | 键对象没有其他引用时,会被 GC 回收 |
| 不可遍历 | 没有 size、没有 clear()、不能 for...of |
| 不会内存泄漏 | 键被回收后,对应的值也会被自动删除 |
4.3 常用方法
javascript
const weakMap = new WeakMap()
let obj = { id: 1 }
weakMap.set(obj, '附加数据')
weakMap.get(obj) // '附加数据'
weakMap.has(obj) // true
weakMap.delete(obj) // true
// 当 obj = null 时,这个键值对会被自动垃圾回收
obj = null
// weakMap 中的这个条目会自动消失
4.4 与 Map 的区别
| 对比 | Map | WeakMap |
|---|---|---|
| 键的类型 | 任意类型 | 只能是对象 |
| 弱引用 | ❌ | ✅ |
| 可遍历 | ✅ | ❌ |
| 有 size | ✅ | ❌ |
| 内存泄漏风险 | 有(需要手动清理) | 无(自动回收) |
4.5 使用场景
| 场景 | 说明 |
|---|---|
| 给 DOM 元素附加数据 | DOM 删除后,数据自动回收 |
| 缓存 | 不影响对象回收 |
| 存储私有数据 | 防止内存泄漏 |
javascript
// 给 DOM 元素附加数据
const weakMap = new WeakMap()
const div = document.getElementById('app')
weakMap.set(div, { clicks: 0 })
div.addEventListener('click', () => {
const data = weakMap.get(div)
data.clicks++
})
// 当 div 被移除后,weakMap 中的数据会自动回收
五、WeakSet(弱引用 Set)
5.1 是什么
弱引用版本的 Set,只能存储对象,不能存储基本类型。
5.2 核心特点
| 特点 | 说明 |
|---|---|
| 只能存对象 | 不能存数字、字符串等基本类型 |
| 弱引用 | 对象被回收时,Set 中自动移除 |
| 不可遍历 | 没有 size、不能 for...of |
5.3 常用方法
javascript
const weakSet = new WeakSet()
let obj1 = { id: 1 }
let obj2 = { id: 2 }
weakSet.add(obj1)
weakSet.add(obj2)
weakSet.has(obj1) // true
weakSet.delete(obj1) // true
// obj2 被回收后,weakSet 中自动移除
obj2 = null
5.4 使用场景
| 场景 | 说明 |
|---|---|
| 标记对象是否已处理 | 防止重复处理 |
| 存储临时对象 | 不影响垃圾回收 |
javascript
// 标记对象是否已处理
const processed = new WeakSet()
function process(item) {
if (processed.has(item)) return
// 处理逻辑...
processed.add(item)
}
六、面试高频问题
Q1:Map 和 Object 的区别?
答:
键的类型:Object 只能是 String/Symbol,Map 可以是任意类型
顺序:Map 保留插入顺序,Object 不严格保证
长度:Map 有
size属性,Object 需要Object.keys().length遍历:Map 可直接
for...of,Object 需要先获取 keys
Q2:Set 如何实现数组去重?
答:
[...new Set(arr)]
Q3:WeakMap 和 Map 的区别?什么时候用 WeakMap?
答:
区别:WeakMap 键只能是对象,弱引用,不可遍历,没有 size
使用场景:给 DOM 元素附加数据、缓存、存储私有数据,避免内存泄漏
Q4:为什么 WeakMap 不可遍历?
答:
因为 WeakMap 是弱引用,键对象可能随时被垃圾回收。如果可遍历,遍历过程中键被回收,会造成不确定性。所以设计成不可遍历。
Q5:[...arr] 和 arr 有什么区别?
javascript
const arr1 = [1, 2, 3]
const arr2 = [...arr1]
arr1 === arr2 // false(不是同一个数组)
arr1[0] === arr2[0] // true(里面的元素是同一个)
Q2:展开运算符和 Object.assign() 有什么区别?
javascript
// 对象复制
const obj = { a: 1, b: 2 }
// 方式1:展开运算符
const copy1 = { ...obj }
// 方式2:Object.assign
const copy2 = Object.assign({}, obj)
// 效果一样,都是浅拷贝
Q3:... 和 rest 参数有什么区别?
javascript
// 展开运算符:数组/对象 → 拆开
const arr = [1, 2, 3]
console.log(...arr) // 1 2 3(拆开)
// rest 参数:多个值 → 收拢成数组
function sum(...args) { // rest 参数,收拢
return args.reduce((a, b) => a + b, 0)
}
sum(1, 2, 3) // 6