JavaScript Set 和 Map 数据结构

一、Set 数据结构

1. 基本概念

Set 是 ES6 引入的一种无序且唯一的值集合,类似于数组,但成员的值都是唯一的。

核心特性:

  • 存储任何类型的唯一值(原始值或对象引用)
  • 值的顺序即插入顺序
  • 通过 === 判断值是否相等(NaN 例外,Set 中 NaN 等于自身)
javascript 复制代码
const set = new Set();
set.add(1);
set.add('text');
set.add({name: 'John'});

console.log(set.size); // 3

2. 常用方法与属性

基本操作:

javascript 复制代码
const set = new Set();

// 添加值
set.add(1).add(2).add(3); // 链式调用

// 检查存在
console.log(set.has(1)); // true

// 删除值
set.delete(1); // 返回布尔值表示是否删除成功

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

// 获取大小
console.log(set.size); // 0

特殊值处理:

javascript 复制代码
const set = new Set();

set.add(NaN);
console.log(set.has(NaN)); // true (与常规NaN !== NaN不同)

set.add(0);
set.add(-0);
console.log(set.size); // 2 (0和-0被视为不同值)

3. 遍历方法

javascript 复制代码
const set = new Set(['red', 'green', 'blue']);

// forEach
set.forEach((value, valueAgain, set) => {
  console.log(value); // red, green, blue
});

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

// 获取迭代器
const iterator = set.values(); // 或 set[Symbol.iterator]()
console.log(iterator.next().value); // "red"

4. 实际应用场景

数组去重:

javascript 复制代码
const numbers = [1, 2, 3, 3, 2, 1];
const unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3]

集合运算:

javascript 复制代码
// 并集
const union = new Set([...setA, ...setB]);

// 交集
const intersection = new Set([...setA].filter(x => setB.has(x)));

// 差集
const difference = new Set([...setA].filter(x => !setB.has(x)));

二、Map 数据结构

1. 基本概念

Map 是键值对的集合,与 Object 类似,但有重要区别:

特性 Map Object
键类型 任意值(对象、函数等) String 或 Symbol
键顺序 插入顺序 无序(ES6后有一定顺序)
大小获取 size 属性 手动计算
性能 频繁增删键值对时更优 少量数据时可能更优
序列化 无原生支持 原生JSON支持
javascript 复制代码
const map = new Map();
map.set('name', 'John');
map.set(1, 'number key');
map.set({}, 'object key');

console.log(map.size); // 3

2. 常用方法与属性

基本操作:

javascript 复制代码
const map = new Map();

// 设置键值
map.set('key', 'value').set('key2', 'value2'); // 链式调用

// 获取值
console.log(map.get('key')); // "value"

// 检查存在
console.log(map.has('key')); // true

// 删除键
map.delete('key'); // 返回布尔值

// 清空Map
map.clear();

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

特殊键处理:

javascript 复制代码
const map = new Map();
const objKey = {};

map.set(objKey, 'value');
console.log(map.get(objKey)); // "value" (对象作为键)

map.set(NaN, 'not a number');
console.log(map.get(NaN)); // "not a number" (NaN作为键)

3. 遍历方法

javascript 复制代码
const map = new Map([
  ['name', 'John'],
  ['age', 30]
]);

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

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

// 获取迭代器
const keys = map.keys(); // 键迭代器
const values = map.values(); // 值迭代器
const entries = map.entries(); // 键值对迭代器

4. 初始化方式

javascript 复制代码
// 通过二维数组初始化
const map1 = new Map([
  ['key1', 'value1'],
  ['key2', 'value2']
]);

// 从Object转换
const obj = { a: 1, b: 2 };
const map2 = new Map(Object.entries(obj));

// 从Map转回Object
const map3 = new Map([['a', 1], ['b', 2]]);
const obj2 = Object.fromEntries(map3);
console.log(obj2); // { a: 1, b: 2 }

三、Set 和 Map 的高级用法

1. 对象作为键的妙用

javascript 复制代码
const user1 = { id: 1, name: 'John' };
const user2 = { id: 2, name: 'Jane' };

const userSettings = new Map();

userSettings.set(user1, { theme: 'dark' });
userSettings.set(user2, { theme: 'light' });

console.log(userSettings.get(user1).theme); // "dark"

2. 实现LRU缓存

javascript 复制代码
class LRUCache {
  constructor(capacity) {
    this.capacity = capacity;
    this.cache = new Map();
  }

  get(key) {
    if (!this.cache.has(key)) return -1;
    
    const value = this.cache.get(key);
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }

  put(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
      // 删除第一个(最久未使用)
      const oldestKey = this.cache.keys().next().value;
      this.cache.delete(oldestKey);
    }
    this.cache.set(key, value);
  }
}

3. 复杂数据结构

javascript 复制代码
// 多层Map
const productLocations = new Map();

const warehouse1 = new Map();
warehouse1.set('A1', { product: 'Phone', quantity: 100 });
warehouse1.set('B2', { product: 'Laptop', quantity: 50 });

const warehouse2 = new Map();
warehouse2.set('C3', { product: 'Tablet', quantity: 200 });

productLocations.set('NY', warehouse1);
productLocations.set('CA', warehouse2);

console.log(productLocations.get('NY').get('A1').quantity); // 100

四、WeakSet 和 WeakMap

1. WeakSet

  • 只能存储对象引用
  • 不可遍历
  • 弱引用(不计入垃圾回收引用计数)
javascript 复制代码
const weakSet = new WeakSet();
let obj = {};

weakSet.add(obj);
console.log(weakSet.has(obj)); // true

obj = null; // 可被垃圾回收

2. WeakMap

  • 键必须是对象
  • 不可遍历
  • 弱引用(键不计入垃圾回收引用计数)
javascript 复制代码
const weakMap = new WeakMap();
let keyObj = { id: 1 };

weakMap.set(keyObj, 'private data');
console.log(weakMap.get(keyObj)); // "private data"

keyObj = null; // 键可被垃圾回收,关联值也会被清除

3. 使用场景

WeakMap 实现私有属性:

javascript 复制代码
const privateData = new WeakMap();

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

const person = new Person('Alice');
console.log(person.getName()); // "Alice"
// 外部无法访问privateData中的数据

WeakSet 标记对象:

javascript 复制代码
const processedItems = new WeakSet();

function process(item) {
  if (processedItems.has(item)) {
    return;
  }
  // 处理逻辑...
  processedItems.add(item);
}

五、性能比较与选择指南

1. Set vs Array

操作 Set Array
查找元素 O(1) O(n)
添加元素 O(1) O(1)
删除元素 O(1) O(n)
去重 内置支持 需要额外处理

选择建议

  • 需要唯一值 → Set
  • 需要索引/顺序操作 → Array
  • 频繁查找/删除 → Set

2. Map vs Object

操作 Map Object
键类型 任意 String/Symbol
键顺序 插入顺序 特殊排序规则
大小获取 size属性 手动计算
频繁增删 更优 较差

选择建议

  • 需要非字符串键 → Map
  • 需要维护插入顺序 → Map
  • 需要JSON序列化 → Object
  • 少量固定属性 → Object

六、浏览器兼容性与Polyfill

1. 兼容性

  • Set/Map:IE11+,所有现代浏览器
  • WeakSet/WeakMap:IE11+,所有现代浏览器

2. Polyfill

对于旧版浏览器,可以使用以下polyfill:

html 复制代码
<!-- 使用core-js的示例 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/minified.js"></script>
<script>
  // 现在可以使用Set/Map
  const set = new Set([1, 2, 3]);
</script>

七、实际应用案例

1. 使用Set实现关键词高亮

javascript 复制代码
function highlightKeywords(text, keywords) {
  const keywordSet = new Set(keywords);
  return text.split(' ').map(word => 
    keywordSet.has(word) ? `<span class="highlight">${word}</span>` : word
  ).join(' ');
}

const result = highlightKeywords('Hello world this is a test', ['world', 'test']);
console.log(result); 
// "Hello <span class="highlight">world</span> this is a <span class="highlight">test</span>"

2. 使用Map实现多语言支持

javascript 复制代码
class I18n {
  constructor() {
    this.translations = new Map();
  }
  
  addLanguage(lang, data) {
    this.translations.set(lang, data);
  }
  
  t(lang, key) {
    const langData = this.translations.get(lang) || {};
    return langData[key] || key;
  }
}

const i18n = new I18n();
i18n.addLanguage('en', { welcome: 'Welcome' });
i18n.addLanguage('es', { welcome: 'Bienvenido' });

console.log(i18n.t('en', 'welcome')); // "Welcome"
console.log(i18n.t('es', 'welcome')); // "Bienvenido"

3. 使用WeakMap实现DOM节点元数据

javascript 复制代码
const nodeMetadata = new WeakMap();

function setNodeData(node, data) {
  nodeMetadata.set(node, data);
}

function getNodeData(node) {
  return nodeMetadata.get(node);
}

const div = document.createElement('div');
setNodeData(div, { clicked: 0 });

div.addEventListener('click', () => {
  const data = getNodeData(div);
  data.clicked++;
  console.log(`Clicked ${data.clicked} times`);
});

八、总结与最佳实践

1. Set/Map 优势总结

  • 更直观的API :专用方法如has(), delete()
  • 更好的性能:大规模数据操作时表现更优
  • 更丰富的键类型:支持对象等复杂类型作为键
  • 维护插入顺序:遍历时按插入顺序返回元素

2. 使用建议

  1. 优先考虑语义:当需要集合语义时使用Set,需要键值对时使用Map
  2. 注意内存管理:大型数据集考虑使用WeakMap/WeakSet避免内存泄漏
  3. 合理初始化:已知初始值时使用构造参数而非多次调用add/set
  4. 利用解构:与扩展运算符结合转换Set/Map为数组
  5. 类型检查 :使用instanceof Set/Map进行类型判断

3. 性能优化技巧

  • 避免频繁的Set/Map创建和销毁
  • 对于大型集合,预先分配足够容量(某些JavaScript引擎优化)
  • 优先使用原生方法而非转换为数组操作
  • WeakMap/WeakSet适合临时关联数据场景
相关推荐
Uyker19 分钟前
微信小程序动态效果实战指南:从悬浮云朵到丝滑列表加载
前端·微信小程序·小程序
小小小小宇43 分钟前
前端按需引入总结
前端
小小小小宇1 小时前
React 的 DOM diff笔记
前端
小小小小宇1 小时前
react和vue DOM diff 简单对比
前端
我在北京coding1 小时前
6套bootstrap后台管理界面源码
前端·bootstrap·html
Carlos_sam1 小时前
Opnelayers:封装Popup
前端·javascript
MessiGo2 小时前
Javascript 编程基础(5)面向对象 | 5.1、构造函数实例化对象
开发语言·javascript·原型模式
前端小白从0开始2 小时前
Vue3项目实现WPS文件预览和内容回填功能
前端·javascript·vue.js·html5·wps·文档回填·文档在线预览
JohnYan2 小时前
Bun技术评估 - 03 HTTP Server
javascript·后端·bun