6.4 Map
Map 是 ES6 引入的一种新的集合类型,为 JavaScript 带来了真正的键值对存储机制。
6.4.1 基本API
Map可以支持使用任何JavaScript数据类型作为键,而Object只能使用数值、字符串和符号作为键。
            
            
              javascript
              
              
            
          
          //使用new创建一个空映射
const m = new Map();
//使用嵌套数组初始化映射
const m1 = new Map([
  ["key1", "val1"],
  ["key2", "val2"],
  ["key3", "val3"],
  ["key4", "val4"],
])
// 初始化后的基本操作
alert(m1.size);										//获取键值对数量:4
m.set("name", "John");           // 设置或添加键值对
m.set({ id: 1 }, "object key");  // 对象作为键
console.log(m.get("name"));      // "John"
console.log(m.has("name"));      // true
m.delete("name");                // 删除
m.clear();                       // 清空6.4.2 顺序与迭代
与Object类型的一个主要差异是,Map实例会维护键值对的插入顺序,因此可以根据顺序进行迭代操作。
            
            
              javascript
              
              
            
          
          const m = new Map([
  ["key1", "val1"],
  ["key2", "val2"],
  ["key3", "val3"]
])
alert(m.entries === m[Symbol.iterator]);	//true
// 遍历方法
m.forEach((value, key) => {
  console.log(`${key}: ${value}`);
});
for (let pair of m[Symbol.iterator]()){
  alert(pair);
}
//[key1,val1]
//[key2,val2]
//[key3,val3]
//entries()获取映射实例的迭代器,返回[key, value]形式的数组。
for (let [key, value] of m.entries()) {
  console.log(key, value);
}
//keys()返回以插入顺序生成键的迭代器
for (let key of m.keys()) {
  console.log(key);
}
//values()返回以插入顺序生成值的迭代器
for (let value of m.values()) {
  console.log(value);
}键和值在迭代器遍历时是可以修改的,但作为键的字符串原始值是不能修改的。如果修改了作为键的对象的属性值,但是对象在映射内部依然引用相同的值,无影响。
            
            
              javascript
              
              
            
          
          const m = new Map([
  ["key1", "value1"]
]);
for(let key of m.keys()){
  key = "newKey"
  console.log(key)  //newKey
  console.log(m.get("key1"))  //value1
}
const keyObj = {id:1};
const m2 = new Map([
  [keyObj, "value1"]
]);
for(let key of m2.keys()){
  key.id = "newKey"
  console.log(key)  //{id: 'newKey'}
  console.log(m2.get(keyObj)) //value1
}
console.log(keyObj);   //{id: 'newKey'}6.4.3 与Object的对比
|------|---------|-------------------|
| 特性   | Map     | Object            |
| 键的类型 | 任何类型    | String 或 Symbol   |
| 键的顺序 | 插入顺序    | 不确定               |
| 大小获取 | size 属性 | 手动计算              |
| 性能   | 频繁增删时更优 | 未优化               |
| 序列化  | 无原生支持   | JSON.stringify 支持 |
6.5 WeakMap
ECMAScript6新增WeakMap"弱映射"是一种新的集合类型。WeakMap 是 Map 的"弱"版本,主要区别在于键必须是Object,且是弱引用,其API也是Map的子集。
6.5.1 基本API
            
            
              javascript
              
              
            
          
          //使用new实例化一个空的WeakMap对象
const wm = new WeakMap();
const key1 = {id:1};
const key2 = {id:2};
const key3 = {id:3};
//使用嵌套数组初始化WeakMap对象 
const wm1 = new WeakMap([
  [key1, 'key1'],
  [key2, 'key2'],
  [key3, 'key3']
]);
//初始化完成后,可以通过多种方法进行操作
console.log(wm1.get(key1)); //使用get()方法获取key1的值
console.log(wm1.has(key1)); //使用has()方法判断key1是否存在
wm1.set(key1, 'key1-new');//使用set()方法设置key1的值
wm1.delete(key1);//使用delete()方法删除key16.5.2 弱键
WeakMap中的键不属于正式的引用,不会阻止垃圾回收。
            
            
              javascript
              
              
            
          
          let obj = { data: "important" };
const wm = new WeakMap();
wm.set(obj, "metadata");
// 当 obj 不再被引用时,垃圾回收器可以回收这个对象
obj = null; // 此时 { data: "important" } 可以被垃圾回收
//然后弱映射中的该键值对也会被清理掉。6.5.3 不可迭代键
因为WeakMap中的键/值对任何时候都可能被销毁,所以没有必要提供迭代能力。也就导致了不可能在不知道对象引用情况下从弱映射中获取值。保证了只有通过键对象的引用才能取得值。
6.5.4 现代应用场景
私有属性实现
私有变量会存储在弱映射中,以对象实例为键,以私有成员的字典为值。
            
            
              javascript
              
              
            
          
          const privateData = new WeakMap();
class User {
  constructor(name, email) {
    // 存储私有数据
    privateData.set(this, {
      name: name,
      email: email,
      internalId: Symbol('id')
    });
  }
  getName() {
    return privateData.get(this).name;
  }
  getEmail() {
    return privateData.get(this).email;
  }
  // 公共属性
  get id() {
    return privateData.get(this).internalId.toString();
  }
}
const user = new User("Alice", "alice@example.com");
console.log(user.getName()); // "Alice"
// 无法直接访问 privateData 中的私有属性DOM 元素元数据
因为WeakMap不会妨碍垃圾回收,因此非常适合保持关联元数据。
            
            
              javascript
              
              
            
          
          const elementMetadata = new WeakMap();
function enhanceElement(element, metadata) {
  elementMetadata.set(element, {
    created: new Date(),
    ...metadata
  });
  // 当 DOM 元素被移除时,对应的元数据会自动被垃圾回收
}
function getElementInfo(element) {
  return elementMetadata.get(element);
}
// 使用示例
const div = document.createElement('div');
enhanceElement(div, { type: 'custom', version: '1.0' });
console.log(getElementInfo(div)); // 显示元数据6.5.5 现代技术中的最佳实践
React/Vue 中的使用
            
            
              javascript
              
              
            
          
          // React 自定义 Hook 中使用 WeakMap
function usePrivateStore() {
  const store = React.useRef(new WeakMap());
  const setPrivate = React.useCallback((key, value) => {
    store.current.set(key, value);
  }, []);
  const getPrivate = React.useCallback((key) => {
    return store.current.get(key);
  }, []);
  return { setPrivate, getPrivate };
}
// Vue 3 组合式 API
export function useComponentState() {
  const states = new WeakMap();
  const setState = (component, state) => {
    states.set(component, reactive(state));
  };
  const getState = (component) => {
    return states.get(component);
  };
  return { setState, getState };
}性能优化模式
            
            
              javascript
              
              
            
          
          // 使用 Map 进行函数记忆化
function memoize(fn) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}
// 使用 WeakMap 进行对象缓存
const renderCache = new WeakMap();
function expensiveRender(element) {
  if (renderCache.has(element)) {
    return renderCache.get(element);
  }
  const result = performExpensiveCalculation(element);
  renderCache.set(element, result);
  return result;
}TypeScript 集成
            
            
              TypeScript
              
              
            
          
          // Map 的 TypeScript 类型
interface User {
  id: number;
  name: string;
}
const userMap: Map<number, User> = new Map();
userMap.set(1, { id: 1, name: "John" });
// WeakMap 的 TypeScript 类型
class Component {
  constructor(public id: string) {}
}
const componentData: WeakMap<Component, { created: Date }> = new WeakMap();
const comp = new Component("btn1");
componentData.set(comp, { created: new Date() });6.5.6 与 Map 的关键区别
|------|-----------|-----------|
| 特性   | WeakMap   | Map       |
| 键类型  | 只能是对象     | 任意类型      |
| 可迭代  | 否         | 是         |
| 可清空  | 否         | clear()   |
| 大小   | 无 size 属性 | 有 size 属性 |
| 垃圾回收 | 键是弱引用     | 键是强引用     |
实际项目中的使用建议
何时使用 Map
- 需要维护键值对的插入顺序时
- 需要频繁添加和删除键值对时
- 键的类型多样(非字符串)时
- 需要知道集合大小时
何时使用 WeakMap
- 需要为对象存储私有数据时
- 管理 DOM 元素的元数据时
- 避免内存泄漏是关键考虑时
- 不需要遍历或知道大小时
6.6 Set
ECMAScript6新增的Set是一种新的集合类型,很多方面像加强版的Map,且大多数API和行为是共有的。它类似于数组,但成员的值都是唯一的。
6.6.1 基本API和迭代方法
与Map类似,Set可以包含任何JavaScript数据类型作为值。Set会维护值插入时的顺序,因此支持按顺序迭代。
            
            
              javascript
              
              
            
          
          //使用new关键字创建一个Set对象
const s = new Set();
//使用数组初始化集合
const s1 = new Set(["val1", "val2", "val3"]);
// 基本操作
s.add(1);                    // 添加值
s.add(2);
s.add(2);                    // 重复添加无效
console.log(s.has(1));       // true
console.log(s.size);         // 2
s.delete(1);                 // 删除值, 返回是否删除成功的布尔值 
s.clear();                   // 清空集合
// 迭代与遍历方法
s.forEach(value => {
  console.log(value);
});
for (let value of s.values()) {
  console.log(value);
}
for (let value of s) {       
  console.log(value);
}
for (let pair of s.entries()) {
  console.log(pair);
}
for (let pair of s[Symbol.iterator]()) {
  console.log(pair);
}6.6.2 现代应用场景
数组去重
            
            
              javascript
              
              
            
          
          // 传统方式
function uniqueArray(arr) {
  return [...new Set(arr)];
}
const numbers = [1, 2, 2, 3, 4, 4, 5];
console.log(uniqueArray(numbers)); // [1, 2, 3, 4, 5]
// 对象数组去重(基于特定属性)
function uniqueByKey(arr, key) {
  const seen = new Set();
  return arr.filter(item => {
    const value = item[key];
    if (seen.has(value)) {
      return false;
    }
    seen.add(value);
    return true;
  });
}数据验证
            
            
              javascript
              
              
            
          
          class FormValidator {
  constructor() {
    this.validators = new Set();
    this.errors = new Set();
  }
  addValidator(validator) {
    this.validators.add(validator);
  }
  validate(data) {
    this.errors.clear();
    for (let validator of this.validators) {
      const error = validator(data);
      if (error) {
        this.errors.add(error);
      }
    }
    return this.errors.size === 0;
  }
  getErrors() {
    return [...this.errors];
  }
}
// 使用示例
const validator = new FormValidator();
validator.addValidator(data => !data.email ? 'Email required' : null);
validator.addValidator(data => data.password?.length < 6 ? 'Password too short' : null);事件管理
            
            
              javascript
              
              
            
          
          class EventEmitter {
  constructor() {
    this.listeners = new Map();
  }
  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event).add(callback);
  }
  off(event, callback) {
    const callbacks = this.listeners.get(event);
    if (callbacks) {
      callbacks.delete(callback);
    }
  }
  emit(event, data) {
    const callbacks = this.listeners.get(event);
    if (callbacks) {
      callbacks.forEach(callback => callback(data));
    }
  }
}6.7 WeakSet
WeakSet 是 Set 的"弱"版本,只能存储对象引用,且是弱引用。
6.7.1 基本API
            
            
              javascript
              
              
            
          
          const ws = new WeakSet();
const obj1 = { id: 1 };
const obj2 = { id: 2 };
ws.add(obj1);
ws.add(obj2);
ws.delete(obj2);
console.log(ws.has(obj1)); // true6.7.2 弱值
WeakSet中的值不属于正式的引用,不会阻碍垃圾回收。
            
            
              javascript
              
              
            
          
          let user = { name: "John" };
const trackedUsers = new WeakSet();
trackedUsers.add(user);
// 当 user 不再被引用时,垃圾回收器可以回收这个对象
user = null; // 此时 { name: "John" } 可以被垃圾回收
//然后弱集合中的该值也会被销毁。6.7.3 与 Set 的关键区别
|------|-----------|-----------|
| 特性   | WeakSet   | Set       |
| 值类型  | 只能是对象     | 任意类型      |
| 可迭代  | 否         | 是         |
| 可清空  | 否         | clear()   |
| 大小   | 无 size 属性 | 有 size 属性 |
| 垃圾回收 | 值是弱引用     | 值是强引用     |
6.7.4 现代应用场景
对象标记
            
            
              javascript
              
              
            
          
          const processedObjects = new WeakSet();
function processObject(obj) {
  // 检查是否已经处理过
  if (processedObjects.has(obj)) {
    console.log('Object already processed');
    return;
  }
  // 处理对象...
  console.log('Processing object:', obj);
  // 标记为已处理
  processedObjects.add(obj);
}
const data = { value: 42 };
processObject(data); // Processing object: {value: 42}
processObject(data); // Object already processedDOM 元素跟踪
            
            
              javascript
              
              
            
          
          const clickedElements = new WeakSet();
document.addEventListener('click', function(event) {
  const target = event.target;
  // 防止重复处理同一元素的多次点击
  if (clickedElements.has(target)) {
    return;
  }
  // 标记元素已被点击
  clickedElements.add(target);
  // 执行点击处理逻辑
  handleFirstClick(target);
});
function handleFirstClick(element) {
  console.log('First click on:', element);
  // 初始化操作...
}私有成员检测
            
            
              javascript
              
              
            
          
          const internalInstances = new WeakSet();
class SecureAPI {
  constructor(apiKey) {
    this.apiKey = apiKey;
    internalInstances.add(this);
  }
  makeRequest(data) {
    // 确保只有通过构造函数创建的对象才能调用此方法
    if (!internalInstances.has(this)) {
      throw new Error('Invalid instance');
    }
    // 安全地执行请求...
    console.log('Making secure request with:', data);
  }
  // 防止外部修改原型链
  static create(apiKey) {
    return new SecureAPI(apiKey);
  }
}6.7.5 实际项目中的使用建议
何时使用 Set
- 需要存储唯一值时
- 需要快速查找、删除操作时
- 进行集合运算(并集、交集等)时
- 管理不重复的列表数据时
何时使用 WeakSet
- 需要标记对象状态而不影响垃圾回收时
- 跟踪对象是否经过特定处理时
- 管理临时对象关联数据时
- 避免内存泄漏是关键考虑时