FORMA

数据结构与集合增强

Map / SetWeakMap / WeakSet、数组高阶方法、TypedArraySymbol 等扩展了内置数据结构能力。见 语言基础

一、Map / WeakMap

MapWeakMap 都是键值对集合,与传统对象的区别在于键的类型不受限制,可以是任意类型(对象、函数、基本类型等)。

1. Map 的特点

  • 键值有序(按插入顺序迭代)。
  • 任何值(包括 NaN)都能作为键。
  • 可以通过 size 属性获取数量。
  • 方法:set(key, value)get(key)has(key)delete(key)clear()keys()values()entries()forEach()
js
const map = new Map();
const objKey = { id: 1 };
map.set(objKey, "value1");
map.set("string", 123);
console.log(map.get(objKey)); // 'value1'
console.log(map.size); // 2

2. WeakMap 的特点

  • 键必须是对象(不能是基本类型)。
  • 弱引用:键被引用的方式为“弱”引用,不会阻止垃圾回收。当没有其他引用指向该键对象时,该键值对会自动从 WeakMap 中移除。
  • 不可迭代(无 size 属性、keys() 等),因为键随时可能消失。
  • 方法:set(key, value)get(key)has(key)delete(key)

典型应用

  • 缓存:将计算结果以对象为键缓存,当对象不再被使用时,缓存自动清理,避免内存泄漏。
  • 关联 DOM 节点数据:将额外数据直接挂载到 DOM 节点上,节点被移除时数据自动回收。
js
// DOM 关联示例
const wm = new WeakMap();
const button = document.querySelector("button");
wm.set(button, { clicks: 0 });
button.addEventListener("click", () => {
    const data = wm.get(button);
    data.clicks++;
});
// 当按钮被移除时,对应的数据对象会被垃圾回收

二、Set / WeakSet

Set 是值不重复的集合,WeakSet 是存储对象且弱引用的集合。

1. Set 的特点

  • 值唯一(使用 SameValueZero 比较,NaN 视为相等)。
  • 可迭代,有 size 属性。
  • 方法:add(value)has(value)delete(value)clear()keys()values()entries()forEach()

常用场景:去重和集合运算

js
// 去重
const arr = [1, 2, 2, 3];
const unique = [...new Set(arr)]; // [1,2,3]

// 集合运算
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);
// 并集
const union = new Set([...setA, ...setB]); // {1,2,3,4}
// 交集
const intersect = new Set([...setA].filter(x => setB.has(x))); // {2,3}
// 差集 (A - B)
const diff = new Set([...setA].filter(x => !setB.has(x))); // {1}

2. WeakSet 的特点

  • 只能存放对象,不能是基本类型。
  • 弱引用,不可迭代,无 size
  • 方法:add(obj)has(obj)delete(obj)

典型应用

  • 标记对象“是否被处理”而不干扰垃圾回收。
js
const processed = new WeakSet();
function process(obj) {
    if (processed.has(obj)) return;
    // 处理逻辑
    processed.add(obj);
}

三、数组高阶方法(常用且深层次的运用)

这些方法是函数式编程的基础,掌握高阶用法能极大简化代码。

1. map – 映射

创建新数组,每个元素由原数组元素经过函数转换而来。

js
const numbers = [1, 2, 3];
const doubled = numbers.map(x => x * 2);
// 注意:可以传入第二个参数绑定 this

2. filter – 过滤

保留满足条件的元素。

js
const evens = numbers.filter(x => x % 2 === 0);

3. reduce – 累计(最强大)

可累积为任何值(数字、数组、对象等)。

js
const sum = numbers.reduce((acc, cur) => acc + cur, 0);
// 深入:实现 map/flatten/管道等
const flat = [
    [1, 2],
    [3, 4],
].reduce((acc, cur) => acc.concat(cur), []); // [1,2,3,4]
// 按属性分组
const people = [
    { name: "A", age: 20 },
    { name: "B", age: 20 },
    { name: "C", age: 21 },
];
const grouped = people.reduce((acc, p) => {
    (acc[p.age] = acc[p.age] || []).push(p);
    return acc;
}, {});

4. some / every – 测试元素

  • some:至少一个满足条件。
  • every:所有满足条件。
js
const hasEven = numbers.some(x => x % 2 === 0); // true
const allPositive = numbers.every(x => x > 0);

5. flatMap – 映射后展平一层

等同于 map().flat(1),但更高效。

js
const arr = ["hello world", "foo bar"];
const words = arr.flatMap(str => str.split(" ")); // ['hello','world','foo','bar']

深层常考应用

  • 链式调用实现数据处理管道。
  • 配合 Object.fromEntries 进行对象转换。
  • 实现惰性求值(借助生成器与高阶函数)。

四、TypedArray、ArrayBuffer、DataView(二进制数据处理)

在 WebGL、Canvas、WebSocket、文件处理等场景需要操作原始二进制数据。

1. ArrayBuffer

代表一段固定长度的原始二进制数据缓冲区(如同 C 语言的 malloc),不能直接读写,需要通过视图操作。

js
const buffer = new ArrayBuffer(16); // 分配 16 字节
console.log(buffer.byteLength); // 16

2. TypedArray(类型数组)

提供类似数组的接口来读写 ArrayBuffer 中的数据,但元素固定为某种数值类型(如 Int8、Uint16、Float64 等)。常见类型:

  • Int8ArrayUint8ArrayUint8ClampedArray(用于 Canvas)
  • Int16ArrayUint16Array
  • Int32ArrayUint32ArrayFloat32ArrayFloat64Array
js
const uint8 = new Uint8Array(buffer); // 视图:每个元素为 8 位无符号整数
uint8[0] = 42;
const int16 = new Int16Array(buffer); // 同一 buffer 的不同视图,数据会重叠
console.log(int16[0]); // 可能不是预期值,因为字节对齐方式不同

3. DataView

更灵活地读写 ArrayBuffer,可以指定字节序(大小端),适合处理混合格式的二进制协议。

js
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setInt16(0, 0x1234, true); // true 表示小端序
console.log(view.getInt16(0, true).toString(16)); // 1234
view.setFloat64(0, 3.14159);

典型应用

  • 处理 WebSocket 二进制帧。
  • 解析文件(如 JPEG、ZIP 头)。
  • WebGL 顶点数据。
  • 高效数值计算(与 WebAssembly 交互)。

五、Symbol 用途

Symbol 是 ES6 引入的第七种原始类型,每个 Symbol() 返回的值都是唯一且不可变的,主要用作对象属性的键。

1. 唯一属性键(避免命名冲突)

js
const id = Symbol("id");
const user = {
    name: "Alice",
    [id]: 12345,
};
console.log(user[id]); // 12345
// Symbol 属性不会出现在 for...in 或 Object.keys() 中,但可通过 Object.getOwnPropertySymbols() 获取

2. 内置 Symbol(用于定制语言行为)

内置符号作用
Symbol.iterator定义默认迭代器(使对象可 for...of
Symbol.asyncIterator定义异步迭代器(用于 for await...of
Symbol.toStringTag修改 Object.prototype.toString.call(obj) 返回的标签
Symbol.hasInstance自定义 instanceof 行为
Symbol.species控制衍生对象构造函数(如 map.filter() 返回的类型)
Symbol.toPrimitive自定义对象转换为原始值的行为
Symbol.unscopables避免某些属性被 with 环境引入

示例:自定义 for...of 行为

js
const iterableObj = {
    data: [1, 2, 3],
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => ({
                value: this.data[index],
                done: index++ >= this.data.length,
            }),
        };
    },
};
for (const val of iterableObj) console.log(val); // 1,2,3

示例:toStringTag

js
class MyClass {}
Object.prototype.toString.call(new MyClass()); // "[object Object]"
MyClass.prototype[Symbol.toStringTag] = "MyClass";
Object.prototype.toString.call(new MyClass()); // "[object MyClass]"

3. 全局注册表(Symbol.forSymbol.keyFor

需要跨模块共享同一个 Symbol 时可使用全局注册表。

js
const s1 = Symbol.for("app.foo");
const s2 = Symbol.for("app.foo");
console.log(s1 === s2); // true
console.log(Symbol.keyFor(s1)); // 'app.foo'

总结

特性核心要点
Map / WeakMap任意键;WeakMap 弱引用对象键,用于缓存和 DOM 关联,自动回收
Set / WeakSet值唯一;WeakSet 存储对象弱引用,标记状态
数组高阶方法map/filter/reduce 等实现函数式数据处理,flatMap 映射展平
ArrayBuffer + 视图操作二进制数据:TypedArray 固定类型存取,DataView 灵活控制字节序
Symbol唯一属性键,内置符号定制语言底层行为(迭代器、toStringhasInstance 等)

掌握这些内容,能让你在处理复杂数据结构、内存密集型任务和元编程时游刃有余。

参考文献

以下链接在编写时均可正常访问:

资料说明
MDN:MapMap
MDN:WeakMapWeakMap
MDN:SetSet
MDN:TypedArray类型数组
MDN:SymbolSymbol

Series

js

9 / 14