Map和Set
map
Map 对象是 JavaScript 中的一种集合数据结构,用于存储键值对。与对象(Object)不同,Map 允许任何类型的值作为键,而不仅仅是字符串或符号。Map 也提供了更多的内置方法来操作键值对,使得它在处理键值对集合时更加灵活和高效。
创建 Map 对象
1 | let myMap = new Map(); |
常用方法
1. set(key, value)
- 用途:向
Map中添加一个新的键值对。 - 示例:
1
2myMap.set("key1", "value1");
myMap.set("key2", "value2");
2. get(key)
- 用途:根据键获取对应的值。如果键不存在,返回
undefined。 - 示例:
1
2console.log(myMap.get("key1")); // "value1"
console.log(myMap.get("key3")); // undefined
3. has(key)
- 用途:检查
Map中是否存在指定的键。返回true或false。 - 示例:
1
2console.log(myMap.has("key1")); // true
console.log(myMap.has("key3")); // false
4. delete(key)
- 用途:从
Map中删除指定的键值对。返回true表示删除成功,false表示键不存在。 - 示例:
1
2console.log(myMap.delete("key1")); // true
console.log(myMap.has("key1")); // false
5. clear()
- 用途:清空
Map中的所有键值对。 - 示例:
1
2myMap.clear();
console.log(myMap.size); // 0
6. size
- 用途:返回
Map中键值对的数量。 - 示例:
1
2
3myMap.set("key1", "value1");
myMap.set("key2", "value2");
console.log(myMap.size); // 2
遍历 Map 对象
Map 对象提供了多种遍历方法,包括 for...of 循环、forEach 方法等。
1. for...of 循环
- 用途:遍历
Map中的每个键值对。 - 示例:
1
2
3
4
5
6for (let [key, value] of myMap) {
console.log(key, value);
}
// 输出:
// key1 value1
// key2 value2
2. forEach 方法
- 用途:遍历
Map中的每个键值对,并对每个键值对执行指定的函数。 - 示例:
1
2
3
4
5
6myMap.forEach((value, key) => {
console.log(key, value);
});
// 输出:
// key1 value1
// key2 value2
键和值的迭代器
Map 对象还提供了 keys()、values() 和 entries() 方法,分别用于获取键、值和键值对的迭代器。
1. keys()
- 用途:返回一个包含所有键的迭代器。
- 示例:
1
2
3
4
5
6for (let key of myMap.keys()) {
console.log(key);
}
// 输出:
// key1
// key2
2. values()
- 用途:返回一个包含所有值的迭代器。
- 示例:
1
2
3
4
5
6for (let value of myMap.values()) {
console.log(value);
}
// 输出:
// value1
// value2
3. entries()
- 用途:返回一个包含所有键值对的迭代器。
- 示例:
1
2
3
4
5
6for (let [key, value] of myMap.entries()) {
console.log(key, value);
}
// 输出:
// key1 value1
// key2 value2
示例:使用 Map 对象
1 | let myMap = new Map(); |
总结
Map 对象是 JavaScript 中处理键值对集合的强大工具,提供了丰富的方法来操作和遍历键值对。与普通对象相比,Map 允许任何类型的值作为键,并且提供了更多的内置方法,使得它在处理复杂数据结构时更加灵活和高效。
map和object的区别
Map 和 Object 都是 JavaScript 中用于存储键值对的数据结构,但它们在功能、性能和使用场景上有一些显著的区别。以下是对 Map 和 Object 的详细对比:
1. 键的类型
Object- 键的类型:键必须是字符串或符号(
Symbol)。其他类型的键会被转换为字符串。 - 示例:
1
2
3
4let obj = {};
obj[1] = "one"; // 键 1 被转换为字符串 "1"
obj[true] = "true"; // 键 true 被转换为字符串 "true"
console.log(obj); // { '1': 'one', 'true': 'true' }
- 键的类型:键必须是字符串或符号(
Map- 键的类型:键可以是任何类型的值,包括对象、函数、数组等。
- 示例:
1
2
3
4
5
6let map = new Map();
map.set(1, "one");
map.set(true, "true");
map.set("key", "value");
map.set({}, "object");
console.log(map); // Map(4) { 1 => 'one', true => 'true', 'key' => 'value', {} => 'object' }
2. 键的顺序
Object- 键的顺序:键的顺序是根据键的类型和插入顺序来决定的。字符串键按插入顺序排序,而数字键按升序排序。
- 示例:
1
2
3
4
5
6let obj = {};
obj[10] = "ten";
obj[2] = "two";
obj[1] = "one";
obj["key"] = "value";
console.log(Object.keys(obj)); // ['1', '2', '10', 'key']
Map- 键的顺序:键的顺序严格按照插入顺序排列。
- 示例:
1
2
3
4
5
6let map = new Map();
map.set(10, "ten");
map.set(2, "two");
map.set(1, "one");
map.set("key", "value");
console.log([...map.keys()]); // [10, 2, 1, 'key']
3. 性能
Object- 性能:在频繁添加和删除键值对时,性能可能会下降,因为
Object的键是字符串,需要进行哈希映射。 - 示例:
1
2
3
4
5let obj = {};
for (let i = 0; i < 1000000; i++) {
obj[i] = i;
}
delete obj[500000];
- 性能:在频繁添加和删除键值对时,性能可能会下降,因为
Map- 性能:在频繁添加和删除键值对时,性能更稳定。
Map专门设计用于高效地处理键值对,特别是在键的类型多样时。 - 示例:
1
2
3
4
5let map = new Map();
for (let i = 0; i < 1000000; i++) {
map.set(i, i);
}
map.delete(500000);
- 性能:在频繁添加和删除键值对时,性能更稳定。
4. 方法和属性
Object- 方法和属性:
Object提供了一些方法和属性,如Object.keys()、Object.values()、Object.entries()等,但这些方法返回的是数组,需要额外的处理。 - 示例:
1
2
3
4let obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj)); // ['a', 'b', 'c']
console.log(Object.values(obj)); // [1, 2, 3]
console.log(Object.entries(obj)); // [['a', 1], ['b', 2], ['c', 3]]
- 方法和属性:
Map- 方法和属性:
Map提供了丰富的内置方法,如set()、get()、has()、delete()、clear()、size等,这些方法和属性直接操作键值对,使用起来更方便。 - 示例:
1
2
3
4
5
6
7
8
9
10
11let map = new Map();
map.set("a", 1);
map.set("b", 2);
map.set("c", 3);
console.log(map.size); // 3
console.log(map.has("a")); // true
console.log(map.get("a")); // 1
map.delete("a");
console.log(map.has("a")); // false
map.clear();
console.log(map.size); // 0
- 方法和属性:
5. 遍历
Object- 遍历:使用
for...in循环遍历对象的键。 - 示例:
1
2
3
4
5
6
7
8let obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
console.log(key, obj[key]);
}
// 输出:
// a 1
// b 2
// c 3
- 遍历:使用
Map- 遍历:使用
for...of循环遍历Map的键值对,也可以使用forEach方法。 - 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18let map = new Map();
map.set("a", 1);
map.set("b", 2);
map.set("c", 3);
for (let [key, value] of map) {
console.log(key, value);
}
// 输出:
// a 1
// b 2
// c 3
map.forEach((value, key) => {
console.log(key, value);
});
// 输出:
// a 1
// b 2
// c 3
- 遍历:使用
6. 总结
Object- 用途:适合存储键为字符串或符号的键值对,适用于简单的数据结构。
- 优点:语法简洁,广泛使用。
- 缺点:键的类型有限,性能在频繁操作时可能下降,方法返回的是数组,需要额外处理。
Map- 用途:适合存储键为任意类型的键值对,特别是在键的类型多样且需要高效操作时。
- 优点:键的类型多样,性能稳定,提供丰富的内置方法,遍历顺序严格按插入顺序。
- 缺点:语法稍复杂,不如
Object使用广泛。
7. 选择建议
- 使用
Object:- 当键为字符串或符号,且不需要频繁添加和删除键值对时。
- 当需要利用对象的原型链或方法时。
- 使用
Map:- 当键的类型多样,特别是包含对象、数组等时。
- 当需要高效地添加和删除键值对时。
- 当需要严格按插入顺序遍历时。
理解Map和Object的这些区别可以帮助你根据具体需求选择合适的数据结构。
Map 和 Object 在实际应用中的区别
1. 存储用户信息
假设我们需要存储用户信息,每个用户有一个唯一的 ID 和一些其他信息,如姓名和年龄。
使用 Object
1 | let users = {}; |
使用 Map
1 | let users = new Map(); |
2. 处理唯一值集合
假设我们需要处理一个数组,去除其中的重复值。
使用 Object
1 | let array = [1, 2, 2, 3, 4, 4, 5]; |
使用 Set
1 | let array = [1, 2, 2, 3, 4, 4, 5]; |
3. 存储复杂键值对
假设我们需要存储一些复杂对象作为键,例如存储用户对象和他们的权限。
使用 Object
1 | let userPermissions = {}; |
使用 Map
1 | let userPermissions = new Map(); |
4. 频繁添加和删除键值对
假设我们需要频繁地添加和删除键值对,例如在一个缓存系统中。
使用 Object
1 | let cache = {}; |
使用 Map
1 | let cache = new Map(); |
总结
Object:- 优点:语法简洁,广泛使用,适合存储键为字符串或符号的简单数据结构。
- 缺点:键的类型有限,性能在频繁操作时可能下降,方法返回的是数组,需要额外处理。
Map:- 优点:键的类型多样,性能稳定,提供丰富的内置方法,遍历顺序严格按插入顺序,适合存储复杂键值对和频繁操作的场景。
- 缺点:语法稍复杂,不如
Object使用广泛。
根据具体需求选择合适的数据结构可以提高代码的可读性和性能。
Set
Set 对象是 JavaScript 中的一种集合数据结构,用于存储唯一的值。Set 中的每个值都只能出现一次,这使得它在处理唯一性数据时非常有用。以下是对 Set 对象的详细解释:
创建 Set 对象
1 | let mySet = new Set(); |
常用方法
1. add(value)
- 用途:向
Set中添加一个新的值。如果值已经存在,则不会重复添加。 - 示例:
1
2
3mySet.add(1);
mySet.add(2);
mySet.add(1); // 1 已经存在,不会重复添加
2. has(value)
- 用途:检查
Set中是否存在指定的值。返回true或false。 - 示例:
1
2console.log(mySet.has(1)); // true
console.log(mySet.has(3)); // false
3. delete(value)
- 用途:从
Set中删除指定的值。返回true表示删除成功,false表示值不存在。 - 示例:
1
2console.log(mySet.delete(1)); // true
console.log(mySet.has(1)); // false
4. clear()
- 用途:清空
Set中的所有值。 - 示例:
1
2mySet.clear();
console.log(mySet.size); // 0
5. size
- 用途:返回
Set中值的数量。 - 示例:
1
2
3mySet.add(1);
mySet.add(2);
console.log(mySet.size); // 2
遍历 Set 对象
Set 对象提供了多种遍历方法,包括 for...of 循环、forEach 方法等。
1. for...of 循环
- 用途:遍历
Set中的每个值。 - 示例:
1
2
3
4
5
6for (let value of mySet) {
console.log(value);
}
// 输出:
// 1
// 2
2. forEach 方法
- 用途:遍历
Set中的每个值,并对每个值执行指定的函数。 - 示例:
1
2
3
4
5
6mySet.forEach((value) => {
console.log(value);
});
// 输出:
// 1
// 2
示例:使用 Set 对象
1 | let mySet = new Set(); |
总结
Set 对象是 JavaScript 中处理唯一值集合的强大工具,提供了丰富的方法来操作和遍历值。与数组相比,Set 自动处理值的唯一性,使得它在处理去重数据时更加高效和方便。以下是一些常见的使用场景:
- 去重:从数组中去除重复值。
1
2
3let array = [1, 2, 2, 3, 4, 4, 5];
let uniqueArray = Array.from(new Set(array));
console.log(uniqueArray); // [1, 2, 3, 4, 5] - 集合操作:进行集合的并集、交集、差集等操作。理解
1
2
3
4
5
6
7
8
9
10
11let set1 = new Set([1, 2, 3]);
let set2 = new Set([2, 3, 4]);
// 并集
let union = new Set([...set1, ...set2]);
console.log(union); // Set(4) {1, 2, 3, 4}
// 交集
let intersection = new Set([...set1].filter(x => set2.has(x)));
console.log(intersection); // Set(2) {2, 3}
// 差集
let difference = new Set([...set1].filter(x => !set2.has(x)));
console.log(difference); // Set(1) {1}Set对象的这些特性和方法可以帮助你更高效地处理唯一值集合。
set和map区别
JavaScript中的Set和Map都是ES6新增的数据结构,它们在功能和用途上有一些区别,以下是详细的对比:
一、存储结构方面
- Set
Set是一个简单的键的集合,其中的元素是唯一的,没有键值对的概念。它的存储结构类似于一个数组,但不允许重复的元素存在。例如:在1
2
3
4
5let mySet = new Set();
mySet.add(1);
mySet.add(2);
mySet.add(2); // 重复的元素不会被添加
console.log(mySet); // Set(2) {1, 2}Set中,每个元素都是唯一的,它通过内部机制来保证元素的唯一性。
- Map
Map是一个键值对的集合,类似于对象(Object),但是它的键可以是任何类型,包括对象、函数等。例如:在1
2
3
4
5let myMap = new Map();
myMap.set('key1', 'value1');
myMap.set(2, 'value2');
myMap.set({objKey: 'objValue'}, 'value3');
console.log(myMap); // Map(3) {'key1' => 'value1', 2 => 'value2', {objKey: 'objValue'} => 'value3'}Map中,每个键都有一个对应的值,而且键可以是任意类型,这使得Map在处理复杂数据结构时更加灵活。
二、操作方法方面
- Set
- 常用方法有
add()(添加元素)、delete()(删除元素)、has()(判断元素是否存在)、clear()(清空所有元素)等。例如:这些方法主要围绕元素的增删查改进行操作,比较简单直接。1
2
3
4
5let mySet = new Set();
mySet.add('apple');
console.log(mySet.has('apple')); // true
mySet.delete('apple');
console.log(mySet.has('apple')); // false
- 常用方法有
- Map
- 常用方法有
set()(设置键值对)、get()(获取键对应的值)、has()(判断键是否存在)、delete()(删除键值对)、clear()(清空所有键值对)等。例如:1
2
3
4
5
6let myMap = new Map();
myMap.set('fruit', 'apple');
console.log(myMap.get('fruit')); // 'apple'
console.log(myMap.has('fruit')); // true
myMap.delete('fruit');
console.log(myMap.has('fruit')); // falseMap的方法主要围绕键值对进行操作,功能更加丰富,可以方便地处理键和值之间的关系。
- 常用方法有
三、遍历方面
- Set
- 可以使用
for...of循环进行遍历,遍历的顺序是元素插入的顺序。例如:也可以使用1
2
3
4
5let mySet = new Set(['apple', 'banana', 'orange']);
for (let item of mySet) {
console.log(item);
}
// 输出:apple banana orangeforEach()方法遍历:1
2
3mySet.forEach((item) => {
console.log(item);
});
- 可以使用
- Map
- 同样可以使用
for...of循环遍历,遍历的顺序也是键值对插入的顺序,每次遍历得到的是一个数组,数组的第一个元素是键,第二个元素是值。例如:也可以使用1
2
3
4
5let myMap = new Map([['fruit', 'apple'], ['color', 'red']]);
for (let [key, value] of myMap) {
console.log(key + ': ' + value);
}
// 输出:fruit: apple color: redforEach()方法遍历:此外,1
2
3myMap.forEach((value, key) => {
console.log(key + ': ' + value);
});Map还可以通过keys()(获取所有键的迭代器)、values()(获取所有值的迭代器)、entries()(获取所有键值对的迭代器)等方法进行更灵活的遍历。
- 同样可以使用
四、用途方面
- Set
- 适用于需要存储唯一元素的场景。例如,去除数组中的重复项:
1
2
3let array = [1, 2, 2, 3, 4, 4, 5];
let uniqueArray = [...new Set(array)];
console.log(uniqueArray); // [1, 2, 3, 4, 5] - 也可以用于判断元素是否存在于集合中,进行一些集合运算(如并集、交集等)。
- 适用于需要存储唯一元素的场景。例如,去除数组中的重复项:
- Map
- 适用于需要存储键值对,并且键可以是任意类型的情况。例如,存储用户信息,其中键是用户对象,值是用户的一些额外信息:
1
2
3
4
5
6let user1 = {name: 'Alice'};
let user2 = {name: 'Bob'};
let userInfoMap = new Map();
userInfoMap.set(user1, {age: 25, gender: 'female'});
userInfoMap.set(user2, {age: 30, gender: 'male'});
console.log(userInfoMap.get(user1)); // {age: 25, gender: 'female'} - 在处理复杂的数据结构和需要频繁访问键值对应关系的场景中非常有用。
- 适用于需要存储键值对,并且键可以是任意类型的情况。例如,存储用户信息,其中键是用户对象,值是用户的一些额外信息:
WeakSet 和 WeakMap
1. WeakSet
定义: WeakSet 是一个集合,其中的元素都是对象,并且这些对象是弱引用的。这意味着如果这些对象没有其他强引用,垃圾回收机制可以自动回收这些对象,WeakSet 中的对应项也会自动被移除。
主要方法:
add(value):向WeakSet中添加一个对象。delete(value):从WeakSet中删除一个对象。has(value):检查WeakSet中是否存在某个对象。
应用场景:
- 避免内存泄漏:存储一组 DOM 节点,当这些节点被移除后,可以自动释放内存。
1
2
3
4
5
6const domNodes = new WeakSet();
const button = document.createElement('button');
button.textContent = 'Click me';
document.body.appendChild(button);
domNodes.add(button);
document.body.removeChild(button); - 跟踪动态数据:跟踪频繁修改的对象,而不会因为长时间保持引用而导致内存泄漏。
1
2
3
4
5
6
7
8const dataPoints = new WeakSet();
function addDataPoint(point) {
dataPoints.add(point);
}
const point1 = { x: 10, y: 20 };
const point2 = { x: 30, y: 40 };
addDataPoint(point1);
addDataPoint(point2); - 存储临时对象:存储临时对象,当这些对象不再需要时,可以自动释放内存。
1
2
3
4
5
6
7const myWeakSet = new WeakSet();
function createTemporaryObject(id) {
const obj = { id: id, data: '/* ... 一些临时数据 ... */' };
myWeakSet.add(obj);
}
createTemporaryObject(1);
createTemporaryObject(2);
2. WeakMap
定义: WeakMap 是一个键值对的集合,其中的键必须是对象,值可以是任意类型。键是弱引用的,这意味着如果这些键对象没有其他强引用,垃圾回收机制可以自动回收这些对象,WeakMap 中的对应键值对也会自动被移除。
主要方法:
set(key, value):向WeakMap中添加或更新一个键值对。get(key):获取对应键的值。delete(key):从WeakMap中删除一个键值对。has(key):检查WeakMap中是否存在某个键。
应用场景:
- 缓存:缓存计算结果或频繁访问的数据,以提高性能。
1
2
3
4
5
6
7
8
9
10
11
12
13const cache = new WeakMap();
function calculation(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = Math.random();
cache.set(obj, result);
return result;
}
const keyObj = {};
console.log(calculation(keyObj));
console.log(calculation(keyObj));
keyObj = null; - 避免内存泄漏:存储监听事件的引用,当监听事件被移除后,可以自动释放内存。
1
2
3
4
5
6
7
8
9
10
11const btn1 = document.querySelector("#btn1");
const btn2 = document.querySelector("#btn2");
const btnMap = new WeakMap();
function handleBtn1Click() {}
function handleBtn2Click() {}
btnMap.set(btn1, handleBtn1Click);
btnMap.set(btn2, handleBtn2Click);
btn1.addEventListener("click", btnMap.get(btn1));
btn2.addEventListener("click", btnMap.get(btn2));
btn1.remove();
btn2.remove(); - 存储私有属性:保护私有属性不被外部访问。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const privateProps = new WeakMap();
class Person {
constructor(name) {
this.name = name;
privateProps.set(this, { age: 0 });
this.incrementAge = () => {
const props = privateProps.get(this);
props.age++;
};
this.getAge = () => {
const props = privateProps.get(this);
return props.age;
};
}
}
const alice = new Person('Alice');
console.log(alice.name); // Alice
console.log(alice.getAge()); // 0
alice.incrementAge();
console.log(alice.getAge()); // 1
console.log(alice.age); // undefined - 优化深拷贝:解决循环引用问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25function deepCopy(obj, visited = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (visited.has(obj)) {
return visited.get(obj);
}
if (obj instanceof Array) {
let copy = [];
visited.set(obj, copy);
for (let i = 0; i < obj.length; i++) {
copy[i] = deepCopy(obj[i], visited);
}
return copy;
}
if (obj instanceof Object) {
let copy = {};
visited.set(obj, copy);
for (let key in obj) {
copy[key] = deepCopy(obj[key], visited);
}
return copy;
}
return obj;
}
总结
- WeakSet:适用于需要存储一组对象引用的场景,这些对象在没有其他强引用时可以被自动回收。常用于避免内存泄漏、跟踪动态数据和存储临时对象。
- WeakMap:适用于需要存储键值对的场景,键是对象,值可以是任意类型。键是弱引用的,当键对象没有其他强引用时,可以被自动回收。常用于缓存、避免内存泄漏、存储私有属性和优化深拷贝。
特点对比
| 特点 | WeakSet | WeakMap |
|---|---|---|
| 键类型 | 只能是对象 | 键必须是对象,值可以是任意类型 |
| 引用类型 | 弱引用 | 弱引用 |
| 可迭代 | 不可迭代 | 不可迭代 |
| 常见方法 | add, delete, has |
set, get, delete, has |
| 应用场景 | 避免内存泄漏、跟踪动态数据、存储临时对象 | 缓存、避免内存泄漏、存储私有属性、优化深拷贝 |
| 希望这些信息对你有所帮助! |