首页
/ JavaScript 集合操作完全指南 - 从基础到高级实践

JavaScript 集合操作完全指南 - 从基础到高级实践

2025-07-08 07:41:37作者:裴锟轩Denise

前言

在 JavaScript 开发中,集合操作是最基础也是最重要的技能之一。本文将全面介绍 JavaScript 中数组、Set 和 Map 的各种操作方法,帮助开发者掌握集合处理的精髓。

数组基础操作

数组的创建方式

JavaScript 提供了多种创建数组的方式,每种方式都有其适用场景:

// 字面量方式 - 最简洁的创建方式
const arr1 = [];

// 构造函数方式 - 与字面量等效
const arr2 = new Array();

// 预分配长度的数组 - 创建包含10个空位的数组
const arr3 = new Array(10);

// 带初始值的数组
const arr4 = [1, true, "string"];

// 构造函数方式带初始值
const arr5 = new Array(1, true, "string");

技术要点

  • 字面量方式是日常开发中最常用的方式
  • 使用 new Array(10) 创建的是包含10个空位的稀疏数组,不是包含10个undefined的数组
  • 数组可以包含任意类型的值,包括混合类型

数组遍历方法

遍历数组是开发中最常见的操作之一,JavaScript 提供了多种遍历方式:

const arr = ['a', 'b', 'c'];

// 传统for循环 - 最基础的方式
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

// for...in循环 - 遍历可枚举属性(包括原型链上的)
for (let index in arr) {
  console.log(arr[index]);
}

// for...of循环 - ES6新增,直接获取值
for (let value of arr) {
  console.log(value);
}

// forEach方法 - 函数式风格
arr.forEach((val) => console.log(val));

最佳实践建议

  1. 纯数组遍历推荐使用 for...offorEach
  2. 需要索引时使用传统 for 循环
  3. 避免使用 for...in 遍历数组,因为它会遍历所有可枚举属性

数组元素操作

添加/删除元素

const arr = [2, 3];

// 尾部添加单个元素
arr.push(4); // [2, 3, 4]

// 头部添加单个元素
arr.unshift(1); // [1, 2, 3, 4]

// 尾部添加多个元素
arr.push(5, 6); // [1, 2, 3, 4, 5, 6]

// 头部添加多个元素
arr.unshift(-1, 0); // [-1, 0, 1, 2, 3, 4, 5, 6]

// 合并数组
const otherArr = [7, 8];
arr.push(...otherArr); // [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8]

注意事项

  • push/pop 操作在数组尾部进行,时间复杂度为 O(1)
  • unshift/shift 操作在数组头部进行,需要移动所有元素,时间复杂度为 O(n)
  • 大数组频繁头部操作应考虑使用其他数据结构

任意位置操作

splice 方法是数组的多功能工具,可以完成插入、删除和替换操作:

const arr = [1, 2, 3, 4, 5];

// 删除操作 - 从索引2开始删除1个元素
arr.splice(2, 1); // 返回 [3],arr变为[1, 2, 4, 5]

// 插入操作 - 在索引2处插入元素
arr.splice(2, 0, 3); // arr恢复为[1, 2, 3, 4, 5]

// 替换操作 - 替换索引1处的元素
arr.splice(1, 1, 'two'); // arr变为[1, 'two', 3, 4, 5]

清空数组的多种方式

let arr = [1, 2, 3];

// 方法1:重新赋值
arr = []; // 最常用方式

// 方法2:设置长度为0
arr.length = 0; // 会立即清空原数组

// 方法3:使用splice
arr.splice(0, arr.length); // 显式清空

// 方法4:循环pop/shift
while(arr.length) arr.pop(); // 性能较差

内存考虑

  • 方法1会创建新数组,原数组如果没有其他引用会被GC回收
  • 其他方法会修改原数组,适用于需要保持数组引用的情况

高级数组技巧

数组类型检测

const arr = [1, 2, 3];

// 标准方法
Array.isArray(arr); // true

// 兼容旧环境的方法
Object.prototype.toString.call(arr) === '[object Array]'; // true

// 不可靠的方法
typeof arr; // "object" - 无法区分数组和普通对象
arr instanceof Array; // 跨frame时会失效

数组模拟栈和队列

// 栈实现 (LIFO)
const stack = [];
stack.push(1); // [1]
stack.push(2); // [1, 2]
stack.pop();   // 返回2, stack变为[1]

// 队列实现 (FIFO)
const queue = [];
queue.push(1); // [1]
queue.push(2); // [1, 2]
queue.shift(); // 返回1, queue变为[2]

性能提示:对于频繁队列操作,shift 性能较差,可以考虑使用两个数组模拟队列或使用专门队列库

处理稀疏数组

const arr = [1,,3]; // 稀疏数组
delete arr[0];      // 制造"洞"

// 过滤undefined值
const denseArr = arr.filter(() => true); // [undefined, 3]

Set 和 Map 数据结构

Set 使用示例

Set 是值唯一的集合:

const set = new Set();
set.add(1).add(2).add(1); // 重复添加无效

console.log(set.size); // 2
console.log(set.has(1)); // true

// 数组去重
const uniqueArray = [...new Set([1,2,2,3])]; // [1,2,3]

Map 使用示例

Map 是键值对集合,键可以是任意类型:

const map = new Map();

// 设置各种类型的键
map.set(1, 'number key');
map.set('1', 'string key');
map.set({}, 'object key');

// 获取值
console.log(map.get(1)); // 'number key'

// 大小
console.log(map.size); // 3

Map 与 Object 的区别

  1. 键类型:Map 键可以是任意值,Object 键只能是字符串或 Symbol
  2. 顺序:Map 保持插入顺序,Object 的键顺序不保证
  3. 大小:Map 有 size 属性,Object 需要手动计算
  4. 性能:Map 在频繁增删场景下性能更好
  5. 默认属性:Object 有原型链上的默认属性

数组高阶函数实现

实现 filter 方法

Array.prototype.myFilter = function(callback, thisArg) {
  if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function');
  }
  
  const result = [];
  for (let i = 0; i < this.length; i++) {
    if (i in this && callback.call(thisArg, this[i], i, this)) {
      result.push(this[i]);
    }
  }
  return result;
};

实现 map 方法

Array.prototype.myMap = function(callback, thisArg) {
  if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function');
  }
  
  const result = new Array(this.length);
  for (let i = 0; i < this.length; i++) {
    if (i in this) {
      result[i] = callback.call(thisArg, this[i], i, this);
    }
  }
  return result;
};

实现 reduce 方法

Array.prototype.myReduce = function(callback, initialValue) {
  if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function');
  }
  
  const isEmpty = this.length === 0;
  let accumulator = initialValue;
  let startIndex = 0;
  
  if (isEmpty && arguments.length < 2) {
    throw new TypeError('Reduce of empty array with no initial value');
  }
  
  if (arguments.length < 2) {
    accumulator = this[0];
    startIndex = 1;
  }
  
  for (let i = startIndex; i < this.length; i++) {
    if (i in this) {
      accumulator = callback(accumulator, this[i], i, this);
    }
  }
  
  return accumulator;
};

实用技巧

简化多重条件判断

// 原始方式
if (browser === 'chrome' || browser === 'firefox' || browser === 'safari') {
  // ...
}

// 使用数组优化
const supportedBrowsers = ['chrome', 'firefox', 'safari'];
if (supportedBrowsers.includes(browser)) {
  // ...
}

二维数组遍历

const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

// 传统for循环
for (let i = 0; i < matrix.length; i++) {
  for (let j = 0; j < matrix[i].length; j++) {
    console.log(matrix[i][j]);
  }
}

// for...of循环
for (const row of matrix) {
  for (const cell of row) {
    console.log(cell);
  }
}

// 函数式风格
matrix.forEach(row => row.forEach(cell => console.log(cell)));

总结

本文全面介绍了 JavaScript 中集合操作的各种方法,从基础的数组创建、遍历,到高级的 Set/Map 使用,再到数组高阶函数的实现原理。掌握这些集合操作技巧,能够显著提高 JavaScript 开发效率和代码质量。在实际开发中,应根据具体场景选择最合适的集合类型和操作方法,以达到最佳的性能和可维护性。