返回首页

04-数组操作全解

分类:01-JavaScript基础核心
发布于:
阅读时间:48 分钟

数组操作全解

📋 学习目标

  • 掌握数组的多种创建方式
  • 熟练使用数组API进行增删改查操作
  • 理解迭代方法与高阶函数的应用
  • 掌握数组性能优化技巧

🎯 数组基础

1. 数组创建方式

数组字面量(推荐)

// 空数组
let arr1 = [];

// 带初始值的数组
let arr2 = [1, 2, 3];
let arr3 = ['apple', 'banana', 'orange'];
let arr4 = [1, 'hello', true, null, undefined, {obj: 'object'}];

// 多维数组
let matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

Array构造函数

// 创建空数组
let arr1 = new Array();

// 创建指定长度的数组
let arr2 = new Array(3); // [empty × 3]
console.log(arr2.length); // 3

// 创建带元素的数组
let arr3 = new Array(1, 2, 3); // [1, 2, 3]

// ⚠️ 陷阱:单个数字参数表示长度
let arr4 = new Array(3); // [empty × 3] 不是 [3]
let arr5 = new Array('3'); // ['3']

ES6新增方法

// Array.of() - 总是返回参数组成的数组
let arr1 = Array.of(3); // [3]
let arr2 = Array.of(1, 2, 3); // [1, 2, 3]

// Array.from() - 将类数组转换为数组
let arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
let arr3 = Array.from(arrayLike); // ['a', 'b', 'c']

// 转换字符串
let arr4 = Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']

// 带映射函数
let arr5 = Array.from([1, 2, 3], x => x * 2); // [2, 4, 6]

🔧 数组操作方法

1. 添加和删除元素

修改原数组的方法

let fruits = ['apple', 'banana'];

// push() - 末尾添加
fruits.push('orange'); // ['apple', 'banana', 'orange']
let length = fruits.push('grape', 'melon'); // 返回新长度

// pop() - 末尾删除
let lastFruit = fruits.pop(); // 'melon'

// unshift() - 开头添加
fruits.unshift('strawberry'); // ['strawberry', 'apple', 'banana', 'orange']

// shift() - 开头删除
let firstFruit = fruits.shift(); // 'strawberry'

// splice() - 任意位置删除、插入、替换
let numbers = [1, 2, 3, 4, 5];

// 删除元素
numbers.splice(2, 1); // 从索引2开始删除1个元素 -> [1, 2, 4, 5]

// 插入元素
numbers.splice(2, 0, 3); // 在索引2处插入3 -> [1, 2, 3, 4, 5]

// 替换元素
numbers.splice(1, 2, 1.5, 2.5); // 替换索引1开始的2个元素

不修改原数组的方法

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

// concat() - 连接数组
let combined = arr1.concat(arr2); // [1, 2, 3, 4, 5, 6]
let combined2 = arr1.concat(7, 8, [9, 10]); // [1, 2, 3, 7, 8, 9, 10]

// slice() - 截取数组
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];

// 基本用法
let slice1 = numbers.slice(2, 5); // [3, 4, 5] (包前不包后)

// 省略第二个参数
let slice2 = numbers.slice(3); // [4, 5, 6, 7, 8, 9]

// 负索引
let slice3 = numbers.slice(-3); // [7, 8, 9]
let slice4 = numbers.slice(2, -2); // [3, 4, 5, 6, 7]

// 复制整个数组
let copy = numbers.slice(); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

2. 查找和过滤

基础查找方法

let users = [
    { id: 1, name: '张三', age: 25, active: true },
    { id: 2, name: '李四', age: 30, active: false },
    { id: 3, name: '王五', age: 35, active: true }
];

// indexOf() - 查找元素索引
let arr = ['apple', 'banana', 'orange'];
let index1 = arr.indexOf('banana'); // 1
let index2 = arr.indexOf('grape'); // -1 (未找到)

// lastIndexOf() - 从后向前查找
let index3 = arr.lastIndexOf('apple'); // 0

// includes() - 判断是否包含
let hasBanana = arr.includes('banana'); // true
let hasGrape = arr.includes('grape'); // false

// find() - 查找第一个匹配的元素
let activeUser = users.find(user => user.active); // { id: 1, name: '张三', age: 25, active: true }

// findIndex() - 查找第一个匹配元素的索引
let activeIndex = users.findIndex(user => user.active); // 0

过滤和判断方法

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// filter() - 过滤元素
let evenNumbers = numbers.filter(num => num % 2 === 0); // [2, 4, 6, 8, 10]
let adults = users.filter(user => user.age >= 30); // [{ id: 2, ... }, { id: 3, ... }]

// some() - 至少有一个满足条件
let hasEven = numbers.some(num => num % 2 === 0); // true
let hasAdult = users.some(user => user.age < 18); // false

// every() - 所有元素都满足条件
let allEven = numbers.every(num => num % 2 === 0); // false
let allActive = users.every(user => user.active); // false

3. 转换和遍历

map() - 转换数组

let numbers = [1, 2, 3, 4, 5];

// 基础映射
let doubled = numbers.map(num => num * 2); // [2, 4, 6, 8, 10]

// 对象数组映射
let userNames = users.map(user => user.name); // ['张三', '李四', '王五']

// 复杂映射
let userCards = users.map(user => ({
    id: user.id,
    fullName: user.name,
    isAdult: user.age >= 18,
    status: user.active ? '在线' : '离线'
}));

forEach() - 遍历数组

let fruits = ['apple', 'banana', 'orange'];

// 基础遍历
fruits.forEach((fruit, index) => {
    console.log(`${index + 1}. ${fruit}`);
});

// 带副作用(修改外部变量)
let sum = 0;
numbers.forEach(num => {
    sum += num;
});
console.log('总和:', sum);

reduce() - 累积计算

let numbers = [1, 2, 3, 4, 5];

// 求和
let sum = numbers.reduce((acc, num) => acc + num, 0); // 15

// 求最大值
let max = numbers.reduce((acc, num) => Math.max(acc, num), -Infinity); // 5

// 数组分组
let people = [
    { name: '张三', age: 25 },
    { name: '李四', age: 30 },
    { name: '王五', age: 25 }
];

let groupedByAge = people.reduce((acc, person) => {
    let age = person.age;
    if (!acc[age]) {
        acc[age] = [];
    }
    acc[age].push(person);
    return acc;
}, {});
// { 25: [{ name: '张三', age: 25 }, { name: '王五', age: 25 }], 30: [{ name: '李四', age: 30 }] }

// 数组去重
let duplicates = [1, 2, 2, 3, 4, 4, 5];
let unique = duplicates.reduce((acc, num) => {
    if (!acc.includes(num)) {
        acc.push(num);
    }
    return acc;
}, []); // [1, 2, 3, 4, 5]

4. 排序和反转

let numbers = [3, 1, 4, 1, 5, 9, 2, 6];

// sort() - 排序(会修改原数组)
let sorted = numbers.slice().sort(); // [1, 1, 2, 3, 4, 5, 6, 9] (字符串排序)

// 数字排序
numbers.sort((a, b) => a - b); // [1, 1, 2, 3, 4, 5, 6, 9] (数字排序)

// 降序排序
numbers.sort((a, b) => b - a); // [9, 6, 5, 4, 3, 2, 1, 1]

// 对象数组排序
users.sort((a, b) => a.age - b.age); // 按年龄升序
users.sort((a, b) => b.age - a.age); // 按年龄降序
users.sort((a, b) => a.name.localeCompare(b.name)); // 按姓名排序

// reverse() - 反转数组
let reversed = numbers.slice().reverse(); // [9, 6, 5, 4, 3, 2, 1, 1]

5. ES6+ 新增方法

// flat() - 数组扁平化
let nested = [1, [2, 3], [4, [5, 6]]];
let flat1 = nested.flat(); // [1, 2, 3, 4, [5, 6]]
let flat2 = nested.flat(2); // [1, 2, 3, 4, 5, 6] (指定深度)

// flatMap() - 映射后扁平化
let sentences = ['Hello World', 'JavaScript is Fun'];
let words = sentences.flatMap(sentence => sentence.split(' '));
// ['Hello', 'World', 'JavaScript', 'is', 'Fun']

// entries(), keys(), values() - 获取迭代器
let arr = ['a', 'b', 'c'];

for (let index of arr.keys()) {
    console.log(index); // 0, 1, 2
}

for (let value of arr.values()) {
    console.log(value); // 'a', 'b', 'c'
}

for (let [index, value] of arr.entries()) {
    console.log(index, value); // 0 'a', 1 'b', 2 'c'
}

// fill() - 填充数组
let fillArr = new Array(5).fill(0); // [0, 0, 0, 0, 0]
let fillArr2 = [1, 2, 3, 4, 5];
fillArr2.fill(0, 1, 3); // [1, 0, 0, 4, 5] (从索引1到3填充0)

🚀 高级应用

1. 数组去重的多种方法

let arr = [1, 2, 2, 3, 4, 4, 5, '1', '2'];

// 方法1:Set + Array.from
let unique1 = Array.from(new Set(arr));

// 方法2:扩展运算符
let unique2 = [...new Set(arr)];

// 方法3:filter + indexOf
let unique3 = arr.filter((item, index) => arr.indexOf(item) === index);

// 方法4:reduce
let unique4 = arr.reduce((acc, item) => {
    return acc.includes(item) ? acc : [...acc, item];
}, []);

// 方法5:对象去重(适用于对象数组)
let users = [
    { id: 1, name: '张三' },
    { id: 2, name: '李四' },
    { id: 1, name: '张三(重复)' }
];

let uniqueUsers = Array.from(new Map(users.map(user => [user.id, user])).values());

2. 数组分块

// 将数组分块
function chunk(array, size) {
    const chunks = [];
    for (let i = 0; i < array.length; i += size) {
        chunks.push(array.slice(i, i + size));
    }
    return chunks;
}

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let chunks = chunk(numbers, 3); // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

// 使用reduce实现分块
const chunkReduce = (array, size) => {
    return array.reduce((acc, cur, i) => {
        const index = Math.floor(i / size);
        if (!acc[index]) {
            acc[index] = [];
        }
        acc[index].push(cur);
        return acc;
    }, []);
};

3. 数组性能优化

// 大数组操作优化
let largeArray = Array.from({ length: 1000000 }, (_, i) => i);

// ❌ 性能较差
let slowFilter = largeArray.filter(x => x % 2 === 0).map(x => x * 2);

// ✅ 性能较好(减少中间数组)
let fastMap = largeArray.reduce((acc, x) => {
    if (x % 2 === 0) {
        acc.push(x * 2);
    }
    return acc;
}, []);

// 使用for循环处理超大数组(性能最佳)
function processLargeArray(array) {
    const result = [];
    for (let i = 0; i < array.length; i++) {
        if (array[i] % 2 === 0) {
            result.push(array[i] * 2);
        }
    }
    return result;
}

⚠️ 常见陷阱

1. 引用类型的问题

// ❌ 浅拷贝问题
let original = [{ name: '对象1' }, { name: '对象2' }];
let copy = original.slice();
copy[0].name = '修改后的对象1';
console.log(original[0].name); // '修改后的对象1' (原数组也被修改)

// ✅ 深拷贝
let deepCopy = JSON.parse(JSON.stringify(original));
let deepCopy2 = original.map(item => ({ ...item }));

2. 稀疏数组陷阱

let sparse = [1, , 3]; // 稀疏数组
console.log(sparse.length); // 3
console.log(sparse[1]); // undefined

// map会跳过空位
let mapped = sparse.map(x => x * 2); // [2, empty, 6]

// filter会移除空位
let filtered = sparse.filter(x => true); // [1, 3]

3. 数组边界问题

let arr = [1, 2, 3];

// 越界访问不会报错
console.log(arr[5]); // undefined

// splice负索引
arr.splice(-1, 1); // 从最后一个开始删除

📝 最佳实践

  1. 优先使用函数式方法:map、filter、reduce等
  2. 避免直接修改原数组:使用slice、扩展运算符创建副本
  3. 选择合适的数据结构:频繁查找时考虑使用Map或Set
  4. 注意性能:大数组操作时考虑使用传统for循环
  5. 类型安全:使用TypeScript增加类型检查

🎯 小结

  • 掌握了数组的多种创建方式
  • 熟练使用各种数组API进行操作
  • 理解了迭代方法和函数式编程
  • 学会了数组性能优化技巧
  • 了解了常见陷阱和解决方案

下一步学习字符串处理方法