数据结构与集合增强
Map / Set、WeakMap / WeakSet、数组高阶方法、TypedArray 与 Symbol 等扩展了内置数据结构能力。见 语言基础。
一、Map / WeakMap
Map 和 WeakMap 都是键值对集合,与传统对象的区别在于键的类型不受限制,可以是任意类型(对象、函数、基本类型等)。
1. Map 的特点
- 键值有序(按插入顺序迭代)。
- 任何值(包括
NaN)都能作为键。 - 可以通过
size属性获取数量。 - 方法:
set(key, value)、get(key)、has(key)、delete(key)、clear()、keys()、values()、entries()、forEach()。
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 节点上,节点被移除时数据自动回收。
// 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()。
常用场景:去重和集合运算
// 去重
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)。
典型应用
- 标记对象“是否被处理”而不干扰垃圾回收。
const processed = new WeakSet();
function process(obj) {
if (processed.has(obj)) return;
// 处理逻辑
processed.add(obj);
}
三、数组高阶方法(常用且深层次的运用)
这些方法是函数式编程的基础,掌握高阶用法能极大简化代码。
1. map – 映射
创建新数组,每个元素由原数组元素经过函数转换而来。
const numbers = [1, 2, 3];
const doubled = numbers.map(x => x * 2);
// 注意:可以传入第二个参数绑定 this
2. filter – 过滤
保留满足条件的元素。
const evens = numbers.filter(x => x % 2 === 0);
3. reduce – 累计(最强大)
可累积为任何值(数字、数组、对象等)。
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:所有满足条件。
const hasEven = numbers.some(x => x % 2 === 0); // true
const allPositive = numbers.every(x => x > 0);
5. flatMap – 映射后展平一层
等同于 map().flat(1),但更高效。
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),不能直接读写,需要通过视图操作。
const buffer = new ArrayBuffer(16); // 分配 16 字节
console.log(buffer.byteLength); // 16
2. TypedArray(类型数组)
提供类似数组的接口来读写 ArrayBuffer 中的数据,但元素固定为某种数值类型(如 Int8、Uint16、Float64 等)。常见类型:
Int8Array、Uint8Array、Uint8ClampedArray(用于 Canvas)Int16Array、Uint16ArrayInt32Array、Uint32Array、Float32Array、Float64Array
const uint8 = new Uint8Array(buffer); // 视图:每个元素为 8 位无符号整数
uint8[0] = 42;
const int16 = new Int16Array(buffer); // 同一 buffer 的不同视图,数据会重叠
console.log(int16[0]); // 可能不是预期值,因为字节对齐方式不同
3. DataView
更灵活地读写 ArrayBuffer,可以指定字节序(大小端),适合处理混合格式的二进制协议。
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. 唯一属性键(避免命名冲突)
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 行为
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
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.for、Symbol.keyFor)
需要跨模块共享同一个 Symbol 时可使用全局注册表。
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 | 唯一属性键,内置符号定制语言底层行为(迭代器、toString、hasInstance 等) |
掌握这些内容,能让你在处理复杂数据结构、内存密集型任务和元编程时游刃有余。
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| MDN:Map | Map |
| MDN:WeakMap | WeakMap |
| MDN:Set | Set |
| MDN:TypedArray | 类型数组 |
| MDN:Symbol | Symbol |
相关文章
错误处理与调试
结构化错误类型、try/catch 与全局未捕获处理是健壮应用的基础。
浏览器相关 API
DOM 事件、存储、fetch、Worker、Observer 等由浏览器提供的 Web API(非 ECMAScript 核心)。HTML 结构见 标签。
性能优化
防抖/节流、渲染与内存、资源预加载及引擎行为是前端性能的主要抓手。相关 CSS 见 视觉效果、Head 资源提示。
模块化与工程化
ESM、CommonJS 与打包工具(Tree Shaking、代码分割)是前端工程化的基础。入门见 JavaScript 基础 · 模块。
执行模型与作用域
执行上下文与词法作用域决定变量在何处可访问、闭包如何形成,以及 var / let / const 的差异。语言概览见 JavaScript 基础。
JavaScript 基础
JavaScript(ECMAScript)是 Web 页面与 Node.js 等运行时的脚本语言:在浏览器中操作 DOM、发起网络请求;在服务端处理 I/O。语法与 HTML、CSS 配合构成前端技术栈。