目录
JavaScript数据结构
一、基础数据结构
1. 数组(Array)
-
定义:有序元素集合,可存储任意类型值。
-
创建方式:
let arr1 = [1, "a", true]; // 字面量 let arr2 = new Array(3); // 创建长度3的空数组(慎用)
-
核心方法:
-
增删元素:
arr.push(4); // 末尾添加 → [1, 2, 3, 4] arr.pop(); // 移除末尾 → [1, 2] arr.unshift(0); // 开头添加 → [0, 1, 2] arr.shift(); // 移除开头 → [1, 2]
-
操作数组:
arr.splice(1, 1, "x"); // 从索引1删除1个元素,插入"x" → [1, "x", 3] arr.slice(1, 3); // 截取索引1到3(不包含3)→ ["x", 3] arr.concat([4, 5]); // 合并数组 → [1, 2, 3, 4, 5]
-
迭代与转换:
arr.map(x => x * 2); // 映射新数组 → [2, 4, 6] arr.filter(x => x > 1); // 过滤 → [2, 3] arr.reduce((sum, x) => sum + x, 0); // 累加 → 6
-
-
注意事项:
-
稀疏数组:空位(如
new Array(3)
)可能引发意外行为。 -
引用类型:数组赋值传递引用,需用
[...arr]
或arr.slice()
克隆。
-
2. 对象(Object)
-
定义:键值对集合,键为字符串或 Symbol。
-
创建方式:
let obj1 = { name: "Alice", age: 25 }; // 字面量 let obj2 = new Object(); // 构造函数
-
核心操作:
-
增删查改:
obj.email = "alice@example.com"; // 添加属性 delete obj.age; // 删除属性 console.log("name" in obj); // 检查属性是否存在 → true
-
遍历:
Object.keys(obj); // 返回键数组 → ["name", "email"] Object.values(obj); // 返回值数组 → ["Alice", "alice@example.com"] for (let key in obj) { console.log(key, obj[key]); } // 遍历自身及原型链属性
-
-
注意事项:
-
原型污染:避免修改
Object.prototype
。 -
键顺序:ES6 后字符串键按插入顺序保留,但数字键优先排序。
-
二、ES6+ 高级数据结构
1. Map
-
特点:
-
键可以是任意类型(对象、函数等)。
-
保持插入顺序。
-
-
核心方法:
let map = new Map(); map.set("key", "value"); // 添加键值对 map.get("key"); // 获取值 → "value" map.has("key"); // 检查存在 → true map.delete("key"); // 删除键值对 map.size; // 获取条目数
-
适用场景:需要复杂键或频繁增删键值对时,优于 Object。
2. Set
-
特点:存储唯一值,自动去重。
-
核心方法:
let set = new Set(); set.add(1); // 添加值 set.add(2); set.has(1); // 检查存在 → true set.delete(1); // 删除值 set.size; // 获取元素数量
-
适用场景:去重、集合运算(并集、交集等)。
3. WeakMap 与 WeakSet
-
特点:
-
弱引用:键必须是对象,不阻止垃圾回收。
-
不可迭代:无
size
、clear()
或遍历方法。
-
-
使用场景:
-
WeakMap:存储对象关联的私有数据或元数据。
let wm = new WeakMap(); let obj = {}; wm.set(obj, "secret");
-
WeakSet:标记对象(如跟踪已处理对象)。
let ws = new WeakSet(); ws.add(obj);
-
三、类型化数组(Typed Arrays)
用于处理二进制数据(如图像、音频),与 ArrayBuffer
配合使用。
-
常见类型:
-
Int8Array
、Uint8Array
(8位整数) -
Int16Array
、Float32Array
等。
-
-
示例:
let buffer = new ArrayBuffer(16); // 分配16字节内存 let int32View = new Int32Array(buffer); // 32位整数视图(4个元素) int32View[0] = 42;
四、其他数据结构实现
JavaScript 未内置,但可通过基础结构模拟:
1. 栈(Stack)
-
后进先出(LIFO),用数组实现:
class Stack { constructor() { this.items = []; } push(element) { this.items.push(element); } pop() { return this.items.pop(); } peek() { return this.items[this.items.length - 1]; } }
2. 队列(Queue)
-
先进先出(FIFO):
class Queue { constructor() { this.items = []; } enqueue(element) { this.items.push(element); } dequeue() { return this.items.shift(); } // 时间复杂度 O(n) }
3. 链表(Linked List)
-
节点链接实现:
class Node { constructor(value) { this.value = value; this.next = null; } } class LinkedList { constructor() { this.head = null; } append(value) { /* 实现添加逻辑 */ } }
五、数据结构选择指南
场景 | 推荐数据结构 | 理由 |
---|---|---|
有序集合,需快速访问索引 | 数组(Array) | 索引操作时间复杂度 O(1) |
键值对,键为字符串或 Symbol | 对象(Object) | 语法简洁,查找速度快 |
键为任意类型,需维护插入顺序 | Map | 支持复杂键,有序 |
存储唯一值 | Set | 自动去重,集合运算高效 |
高频增删元素(如队列) | 链表(自定义实现) | 避免数组 shift() 的 O(n) 复杂度 |
二进制数据处理 | 类型化数组(Typed Array) | 内存高效,适合底层操作 |
六、最佳实践
-
优先使用 ES6+ 数据结构:如
Map
、Set
替代传统对象和数组,提升代码可读性。 -
注意引用类型副作用:克隆数据避免意外修改。
-
性能敏感场景优化:如用
Int32Array
替代普通数组处理大量数值。 -
垃圾回收考虑:使用
WeakMap
/WeakSet
管理对象关联数据,防止内存泄漏。
JavaScript中的for
循环
1. 传统 for
循环
最基础的循环结构,通过明确的初始化、条件和迭代器控制循环。
语法
for (初始化; 条件; 迭代器) {
// 循环体
}
示例
// 遍历数组
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]); // 输出 1, 2, 3
}
// 倒序循环
for (let i = arr.length - 1; i >= 0; i--) {
console.log(arr[i]); // 输出 3, 2, 1
}
特点
-
灵活控制:可自定义步长(如
i += 2
)、跳过某次循环(continue
)或提前退出(break
)。 -
性能优化:预存数组长度(如
let len = arr.length
)避免重复计算。 -
作用域:使用
let
声明变量时,每次循环会创建新的块级作用域。
2. for...of
循环
遍历可迭代对象(如数组、字符串、Map、Set、生成器等)的值。
语法
for (const value of iterable) {
// 使用 value
}
示例
const arr = ['a', 'b', 'c'];
for (const val of arr) {
console.log(val); // 输出 'a', 'b', 'c'
}
// 遍历字符串
for (const char of 'Hello') {
console.log(char); // 输出 H, e, l, l, o
}
特点
-
简洁性:无需索引,直接获取值。
-
支持异步:可与
await
结合使用(需在async
函数中)。 -
不适用于普通对象:默认对象不可迭代(除非自行实现
Symbol.iterator
)。
3. for...in
循环
遍历对象的可枚举属性(包括原型链上的属性)。
语法
for (const key in object) {
// 使用 key 访问属性值:object[key]
}
示例
const obj = { a: 1, b: 2 };
for (const key in obj) {
console.log(key, obj[key]); // 输出 a 1, b 2
}
// 遍历数组(不推荐!)
const arr = [1, 2, 3];
arr.foo = 'bar';
for (const key in arr) {
console.log(key); // 输出 0, 1, 2, 'foo'
}
特点
-
遍历对象属性:适合处理键值对。
-
可能遍历原型链:需用
hasOwnProperty
过滤:for (const key in obj) { if (obj.hasOwnProperty(key)) { // 仅处理自身属性 } }
-
不保证顺序:现代引擎通常按属性添加顺序遍历,但复杂场景可能不一致。
4. Array.prototype.forEach
数组专用的高阶函数,遍历每个元素。
语法
array.forEach((value, index, array) => {
// 循环体
});
示例
const arr = [1, 2, 3];
arr.forEach((value, index) => {
console.log(index, value); // 输出 0 1, 1 2, 2 3
});
特点
-
不可中断:无法使用
break
或continue
,需通过return
跳过当前迭代。 -
链式调用:可与其他数组方法(如
map
、filter
)配合。 -
异步陷阱:回调函数中的
await
不会暂停外层循环。
5. 性能与选择指南
性能对比
-
最快:传统
for
循环(尤其预存长度时)。 -
适中:
for...of
(底层使用迭代器协议)。 -
较慢:
forEach
(函数调用开销)。
如何选择?
场景 | 推荐方式 |
---|---|
需要索引或复杂控制 | 传统 for 循环 |
遍历数组值(无需索引) | for...of |
遍历对象属性 | for...in + 过滤 |
简单数组操作 | forEach |
处理稀疏数组 | 传统 for + 判空 |
6. 常见陷阱
1. 修改数组长度
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
arr.pop(); // 可能导致无限循环或跳过元素
}
2. 闭包问题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 输出 3, 3, 3
}
// 解决:改用 let 或 IIFE
3. 遍历稀疏数组
const arr = [1, , 3]; // 中间是空位
arr.forEach(v => console.log(v)); // 输出 1, 3(跳过空位)
for (const v of arr) console.log(v); // 输出 1, undefined, 3