Map与Set

Set基本用法

Set是值的集合,值唯一。

创建Set

// 创建空Set
const set1 = new Set();

// 从数组创建
const set2 = new Set([1, 2, 3, 2, 1]);
console.log(set2);  // Set { 1, 2, 3 }

// 从字符串创建
const set3 = new Set("hello");
console.log(set3);  // Set { "h", "e", "l", "o" }

Set基本操作

const set = new Set();

// 添加值
set.add(1);
set.add(2);
set.add(2);  // 重复值会被忽略
console.log(set.size);  // 2

// 链式添加
set.add(3).add(4).add(5);

// 检查是否存在
console.log(set.has(1));  // true
console.log(set.has(6));  // false

// 删除值
set.delete(3);
console.log(set.has(3));  // false

// 清空
// set.clear();

// 转换为数组
const arr = [...set];
console.log(arr);  // [1, 2, 4, 5]

// 或使用Array.from
const arr2 = Array.from(set);

Set遍历

const set = new Set(["东巴文", "db-w.cn"]);

// for...of
for (const value of set) {
    console.log(value);
}

// forEach
set.forEach((value, key, set) => {
    console.log(value);  // Set中key和value相同
});

// keys(), values(), entries()
console.log([...set.keys()]);    // ["东巴文", "db-w.cn"]
console.log([...set.values()]);  // ["东巴文", "db-w.cn"]
console.log([...set.entries()]); // [["东巴文", "东巴文"], ["db-w.cn", "db-w.cn"]]

Set方法

Set的实用方法。

数组去重

// 数组去重
const arr = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(arr)];
console.log(unique);  // [1, 2, 3]

// 对象数组去重
const users = [
    { id: 1, name: "东巴文" },
    { id: 2, name: "db-w.cn" },
    { id: 1, name: "东巴文" }
];

const uniqueUsers = [...new Map(users.map(u => [u.id, u])).values()];
console.log(uniqueUsers);

集合运算

const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);

// 并集
const union = new Set([...a, ...b]);
console.log([...union]);  // [1, 2, 3, 4]

// 交集
const intersection = new Set([...a].filter(x => b.has(x)));
console.log([...intersection]);  // [2, 3]

// 差集
const difference = new Set([...a].filter(x => !b.has(x)));
console.log([...difference]);  // [1]

// 对称差集
const symmetricDiff = new Set([
    ...[...a].filter(x => !b.has(x)),
    ...[...b].filter(x => !a.has(x))
]);
console.log([...symmetricDiff]);  // [1, 4]

判断子集

// 判断是否是子集
function isSubset(subset, superset) {
    for (const value of subset) {
        if (!superset.has(value)) {
            return false;
        }
    }
    return true;
}

const a = new Set([1, 2]);
const b = new Set([1, 2, 3, 4]);

console.log(isSubset(a, b));  // true
console.log(isSubset(b, a));  // false

WeakSet

WeakSet是弱引用的Set。

WeakSet特点

// WeakSet只能存储对象
const weakSet = new WeakSet();

const obj1 = { name: "东巴文" };
const obj2 = { name: "db-w.cn" };

weakSet.add(obj1);
weakSet.add(obj2);

console.log(weakSet.has(obj1));  // true

// 不能存储原始值
// weakSet.add(1);  // TypeError

// 特点:
// 1. 只能存储对象
// 2. 弱引用,不影响垃圾回收
// 3. 不可遍历
// 4. 没有size属性

WeakSet应用

// 存储DOM元素引用
const disabledElements = new WeakSet();

function disable(element) {
    disabledElements.add(element);
    element.disabled = true;
}

function enable(element) {
    disabledElements.delete(element);
    element.disabled = false;
}

function isDisabled(element) {
    return disabledElements.has(element);
}

// 对象被删除后,WeakSet中的引用自动清除

Map基本用法

Map是键值对集合。

创建Map

// 创建空Map
const map1 = new Map();

// 从数组创建
const map2 = new Map([
    ["name", "东巴文"],
    ["age", 25]
]);
console.log(map2.get("name"));  // 东巴文

// 从对象创建
const obj = { x: 1, y: 2 };
const map3 = new Map(Object.entries(obj));
console.log(map3.get("x"));  // 1

Map基本操作

const map = new Map();

// 设置键值对
map.set("name", "东巴文");
map.set("age", 25);

// 链式设置
map.set("email", "info@db-w.cn").set("url", "db-w.cn");

// 获取值
console.log(map.get("name"));     // 东巴文
console.log(map.get("unknown"));  // undefined

// 检查键是否存在
console.log(map.has("name"));  // true
console.log(map.has("x"));     // false

// 获取大小
console.log(map.size);  // 4

// 删除
map.delete("age");
console.log(map.has("age"));  // false

// 清空
// map.clear();

Map键的类型

const map = new Map();

// 各种类型的键
map.set("string", "字符串键");
map.set(123, "数字键");
map.set(true, "布尔键");
map.set(undefined, "undefined键");
map.set(null, "null键");
map.set({ id: 1 }, "对象键");
map.set([1, 2], "数组键");
map.set(() => {}, "函数键");
map.set(NaN, "NaN键");

// NaN作为键
map.set(NaN, "NaN");
console.log(map.get(NaN));  // "NaN"
console.log(map.has(NaN));  // true

// 对象键是引用比较
const obj1 = { id: 1 };
const obj2 = { id: 1 };
map.set(obj1, "对象1");
map.set(obj2, "对象2");
console.log(map.get(obj1));  // "对象1"
console.log(map.get(obj2));  // "对象2"

Map方法

Map的遍历和转换方法。

Map遍历

const map = new Map([
    ["name", "东巴文"],
    ["age", 25],
    ["url", "db-w.cn"]
]);

// for...of
for (const [key, value] of map) {
    console.log(key, value);
}

// forEach
map.forEach((value, key, map) => {
    console.log(key, value);
});

// keys()
for (const key of map.keys()) {
    console.log(key);
}

// values()
for (const value of map.values()) {
    console.log(value);
}

// entries()
for (const [key, value] of map.entries()) {
    console.log(key, value);
}

Map转换

const map = new Map([
    ["name", "东巴文"],
    ["age", 25]
]);

// 转换为数组
const arr = [...map];
console.log(arr);  // [["name", "东巴文"], ["age", 25]]

// 转换为对象
const obj = Object.fromEntries(map);
console.log(obj);  // { name: "东巴文", age: 25 }

// 对象转Map
const obj2 = { x: 1, y: 2 };
const map2 = new Map(Object.entries(obj2));
console.log(map2.get("x"));  // 1

Map与Object比较

Map和Object的区别。

主要区别

// 1. 键的类型
// Object: 只能是字符串或Symbol
// Map: 任意类型

// 2. 键的顺序
// Object: 无序(整数键排序在前)
// Map: 插入顺序

// 3. 大小
// Object: 需要手动计算
// Map: size属性

const obj = {};
obj[2] = "two";
obj[1] = "one";
obj["a"] = "a";
console.log(Object.keys(obj));  // ["1", "2", "a"]

const map = new Map();
map.set(2, "two");
map.set(1, "one");
map.set("a", "a");
console.log([...map.keys()]);  // [2, 1, "a"]

// 4. 原型链
// Object: 有原型链,可能有默认键
// Map: 干净的键值对

console.log("toString" in {});  // true
console.log("toString" in Object.create(null));  // false

选择建议

// 使用Map的场景:
// 1. 键的类型不确定
// 2. 需要保持插入顺序
// 3. 频繁增删键值对
// 4. 需要知道大小

// 使用Object的场景:
// 1. 存储记录/实体
// 2. 需要JSON序列化
// 3. 键是字符串或Symbol
// 4. 作为配置对象

// 性能对比
const map = new Map();
const obj = {};

// 大量操作时Map更快
console.time("Map");
for (let i = 0; i < 100000; i++) {
    map.set(i, i);
}
console.timeEnd("Map");

console.time("Object");
for (let i = 0; i < 100000; i++) {
    obj[i] = i;
}
console.timeEnd("Object");

WeakMap

WeakMap是弱引用的Map。

WeakMap特点

// WeakMap的键必须是对象
const weakMap = new WeakMap();

const obj = { name: "东巴文" };
weakMap.set(obj, "值");

console.log(weakMap.get(obj));  // "值"

// 特点:
// 1. 键必须是对象
// 2. 弱引用键,不影响垃圾回收
// 3. 不可遍历
// 4. 没有size属性
// 5. 没有keys(), values(), entries()

WeakMap应用

// 存储私有数据
const privateData = new WeakMap();

class Person {
    constructor(name, age) {
        privateData.set(this, { name, age });
    }
    
    get name() {
        return privateData.get(this).name;
    }
    
    get age() {
        return privateData.get(this).age;
    }
}

const person = new Person("东巴文", 25);
console.log(person.name);  // 东巴文

// 缓存计算结果
const cache = new WeakMap();

function compute(obj) {
    if (cache.has(obj)) {
        return cache.get(obj);
    }
    
    const result = expensiveCalculation(obj);
    cache.set(obj, result);
    return result;
}

// DOM关联数据
const elementData = new WeakMap();

function setData(element, key, value) {
    if (!elementData.has(element)) {
        elementData.set(element, new Map());
    }
    elementData.get(element).set(key, value);
}

function getData(element, key) {
    return elementData.get(element)?.get(key);
}

下一步

掌握了Map与Set后,让我们继续学习:

  1. 模块化概述 - 学习模块化开发
  2. CommonJS - 学习CommonJS规范
  3. [ES Modules](./59_ES Modules.md) - 学习ES模块

东巴文(db-w.cn) - 让编程学习更简单

🎯 东巴文寄语:Map和Set是ES6新增的集合类型,相比Object和Array,它们提供了更灵活的键值存储和更高效的值唯一性保证。在 db-w.cn,我们帮你掌握现代JavaScript数据结构!