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/core-js-bundle@3.6.5/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适合临时关联数据场景
相关推荐
WeiXiao_Hyy39 分钟前
成为 Top 1% 的工程师
java·开发语言·javascript·经验分享·后端
吃杠碰小鸡1 小时前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone1 小时前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_09011 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农2 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king2 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳2 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵3 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星3 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_3 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js