JavaScript中的Array对象及其方法详解

222 阅读8分钟

JavaScript中的Array对象及其方法详解

骚话王又来分享知识了!今天咱们来聊聊JavaScript中的Array对象,这个在前端开发中几乎天天都要打交道的老朋友。Array可以说是JavaScript中最常用的数据结构之一,从简单的数据存储到复杂的数据处理,都离不开它。

Array对象的基本概念

Array(数组)是JavaScript中的一种有序集合,可以存储任意类型的数据。它就像一个可以装各种东西的容器,而且这个容器是有序的,每个元素都有自己的位置编号(索引)。

创建数组的几种方式

// 1. 数组字面量(最常用)
const fruits = ['苹果', '香蕉', '橙子'];

// 2. Array构造函数
const numbers = new Array(1, 2, 3, 4, 5);

// 3. 创建指定长度的空数组
const emptyArray = new Array(5); // [undefined, undefined, undefined, undefined, undefined]

// 4. Array.of() - 创建包含单个元素的数组
const singleElement = Array.of(7); // [7]

// 5. Array.from() - 从类数组对象创建数组
const arrayFromString = Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']
const arrayFromSet = Array.from(new Set([1, 2, 2, 3])); // [1, 2, 3]

数组的基本特性

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

// 访问元素(索引从0开始)
console.log(arr[0]); // 'a'
console.log(arr[arr.length - 1]); // 'c' - 最后一个元素

// 修改元素
arr[1] = 'x';
console.log(arr); // ['a', 'x', 'c']

// 数组长度
console.log(arr.length); // 3

// 动态调整长度
arr.length = 5; // 扩展数组,新增的元素为undefined
console.log(arr); // ['a', 'x', 'c', undefined, undefined]

arr.length = 2; // 截断数组
console.log(arr); // ['a', 'x']

数组的静态方法

Array.isArray() - 判断是否为数组

这个方法特别实用,因为JavaScript中typeof对数组返回的是'object',所以我们需要专门的方法来判断:

const arr = [1, 2, 3];
const obj = { a: 1 };

console.log(typeof arr); // 'object' - 这就不太靠谱
console.log(Array.isArray(arr)); // true
console.log(Array.isArray(obj)); // false
console.log(Array.isArray(null)); // false
console.log(Array.isArray(undefined)); // false

// 实际应用场景
function processData(data) {
    if (Array.isArray(data)) {
        return data.map(item => item * 2);
    } else {
        throw new Error('数据必须是数组!');
    }
}

Array.from() - 从类数组对象创建数组

这个方法特别强大,可以把很多类数组对象转换成真正的数组:

// 从字符串创建数组
const chars = Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']

// 从Set创建数组
const uniqueNumbers = Array.from(new Set([1, 2, 2, 3, 3, 4])); // [1, 2, 3, 4]

// 从Map创建数组
const map = new Map([['a', 1], ['b', 2]]);
const mapArray = Array.from(map); // [['a', 1], ['b', 2]]

// 从DOM节点列表创建数组
const divs = document.querySelectorAll('div');
const divArray = Array.from(divs); // 现在可以使用数组方法了

// 创建数字序列
const range = Array.from({length: 5}, (_, i) => i); // [0, 1, 2, 3, 4]
const range2 = Array.from({length: 5}, (_, i) => i * 2); // [0, 2, 4, 6, 8]

// 实际应用:生成随机数数组
const randomNumbers = Array.from({length: 10}, () => Math.floor(Math.random() * 100));

Array.of() - 创建包含指定元素的数组

这个方法解决了Array构造函数的一个小坑:

// Array构造函数的坑
console.log(new Array(3)); // [undefined, undefined, undefined]
console.log(new Array(3, 4)); // [3, 4]

// Array.of() 更直观
console.log(Array.of(3)); // [3]
console.log(Array.of(3, 4)); // [3, 4]
console.log(Array.of()); // []

// 实际应用
function createArray(...args) {
    return Array.of(...args);
}

const result = createArray(1, 2, 3); // [1, 2, 3]

数组的实例方法

添加和删除元素

const arr = ['苹果', '香蕉'];

// push() - 在末尾添加元素
arr.push('橙子'); // 返回新长度:3
console.log(arr); // ['苹果', '香蕉', '橙子']

// pop() - 删除并返回最后一个元素
const lastFruit = arr.pop(); // '橙子'
console.log(arr); // ['苹果', '香蕉']

// unshift() - 在开头添加元素
arr.unshift('葡萄'); // 返回新长度:3
console.log(arr); // ['葡萄', '苹果', '香蕉']

// shift() - 删除并返回第一个元素
const firstFruit = arr.shift(); // '葡萄'
console.log(arr); // ['苹果', '香蕉']

// 实际应用:队列和栈的实现
class Queue {
    constructor() {
        this.items = [];
    }
    
    enqueue(item) {
        this.items.push(item);
    }
    
    dequeue() {
        return this.items.shift();
    }
    
    peek() {
        return this.items[0];
    }
    
    isEmpty() {
        return this.items.length === 0;
    }
}

class Stack {
    constructor() {
        this.items = [];
    }
    
    push(item) {
        this.items.push(item);
    }
    
    pop() {
        return this.items.pop();
    }
    
    peek() {
        return this.items[this.items.length - 1];
    }
    
    isEmpty() {
        return this.items.length === 0;
    }
}

数组的查找方法

const fruits = ['苹果', '香蕉', '橙子', '葡萄', '苹果'];

// indexOf() - 查找元素第一次出现的索引
console.log(fruits.indexOf('苹果')); // 0
console.log(fruits.indexOf('西瓜')); // -1 (没找到)

// lastIndexOf() - 查找元素最后一次出现的索引
console.log(fruits.lastIndexOf('苹果')); // 4

// includes() - 检查数组是否包含某个元素
console.log(fruits.includes('香蕉')); // true
console.log(fruits.includes('西瓜')); // false

// find() - 查找第一个满足条件的元素
const numbers = [1, 2, 3, 4, 5, 6];
const firstEven = numbers.find(num => num % 2 === 0); // 2

// findIndex() - 查找第一个满足条件的元素的索引
const firstEvenIndex = numbers.findIndex(num => num % 2 === 0); // 1

// findLast() - 查找最后一个满足条件的元素
const lastEven = numbers.findLast(num => num % 2 === 0); // 6

// findLastIndex() - 查找最后一个满足条件的元素的索引
const lastEvenIndex = numbers.findLastIndex(num => num % 2 === 0); // 5

// 实际应用:用户搜索
const users = [
    { id: 1, name: '张三', age: 25 },
    { id: 2, name: '李四', age: 30 },
    { id: 3, name: '王五', age: 28 }
];

function findUserByName(name) {
    return users.find(user => user.name === name);
}

function findUsersByAge(age) {
    return users.filter(user => user.age === age);
}

console.log(findUserByName('李四')); // { id: 2, name: '李四', age: 30 }

数组的转换方法

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

// map() - 转换每个元素
const doubled = numbers.map(num => num * 2); // [2, 4, 6, 8, 10]
const strings = numbers.map(num => num.toString()); // ['1', '2', '3', '4', '5']

// filter() - 过滤元素
const evens = numbers.filter(num => num % 2 === 0); // [2, 4]
const odds = numbers.filter(num => num % 2 !== 0); // [1, 3, 5]

// reduce() - 归约数组
const sum = numbers.reduce((acc, num) => acc + num, 0); // 15
const product = numbers.reduce((acc, num) => acc * num, 1); // 120

// reduceRight() - 从右到左归约
const rightSum = numbers.reduceRight((acc, num) => acc + num, 0); // 15

// 实际应用:数据处理
const orders = [
    { id: 1, amount: 100, status: 'completed' },
    { id: 2, amount: 200, status: 'pending' },
    { id: 3, amount: 150, status: 'completed' },
    { id: 4, amount: 300, status: 'cancelled' }
];

// 计算已完成订单的总金额
const completedTotal = orders
    .filter(order => order.status === 'completed')
    .reduce((sum, order) => sum + order.amount, 0);

console.log(completedTotal); // 250

// 按状态分组
const groupedOrders = orders.reduce((groups, order) => {
    if (!groups[order.status]) {
        groups[order.status] = [];
    }
    groups[order.status].push(order);
    return groups;
}, {});

console.log(groupedOrders);
// {
//   completed: [{ id: 1, amount: 100, status: 'completed' }, { id: 3, amount: 150, status: 'completed' }],
//   pending: [{ id: 2, amount: 200, status: 'pending' }],
//   cancelled: [{ id: 4, amount: 300, status: 'cancelled' }]
// }

数组的排序和反转

const fruits = ['香蕉', '苹果', '橙子', '葡萄'];

// sort() - 排序(默认按字符串排序)
fruits.sort(); // ['苹果', '葡萄', '橙子', '香蕉']

// 数字排序
const numbers = [3, 1, 4, 1, 5, 9, 2, 6];
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]

// 对象排序
const users = [
    { name: '张三', age: 25 },
    { name: '李四', age: 30 },
    { name: '王五', age: 20 }
];

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

// reverse() - 反转数组
const reversed = fruits.slice().reverse(); // 创建副本再反转
console.log(reversed); // ['香蕉', '橙子', '葡萄', '苹果']

// 实际应用:多条件排序
const products = [
    { name: '手机', price: 2000, rating: 4.5 },
    { name: '电脑', price: 5000, rating: 4.8 },
    { name: '耳机', price: 200, rating: 4.2 },
    { name: '平板', price: 3000, rating: 4.5 }
];

// 按评分降序,评分相同时按价格升序
products.sort((a, b) => {
    if (a.rating !== b.rating) {
        return b.rating - a.rating; // 评分降序
    }
    return a.price - b.price; // 价格升序
});

数组的切片和拼接

const fruits = ['苹果', '香蕉', '橙子', '葡萄', '西瓜'];

// slice() - 切片(不修改原数组)
const slice1 = fruits.slice(1, 3); // ['香蕉', '橙子']
const slice2 = fruits.slice(-2); // ['葡萄', '西瓜']
const slice3 = fruits.slice(1, -1); // ['香蕉', '橙子', '葡萄']

// splice() - 删除和插入元素(修改原数组)
const fruitsCopy = [...fruits];
fruitsCopy.splice(1, 2); // 从索引1开始删除2个元素
console.log(fruitsCopy); // ['苹果', '葡萄', '西瓜']

const fruitsCopy2 = [...fruits];
fruitsCopy2.splice(1, 0, '梨子'); // 在索引1处插入'梨子'
console.log(fruitsCopy2); // ['苹果', '梨子', '香蕉', '橙子', '葡萄', '西瓜']

// concat() - 连接数组
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = arr1.concat(arr2); // [1, 2, 3, 4, 5, 6]

// 实际应用:分页功能
function paginate(array, page, pageSize) {
    const start = (page - 1) * pageSize;
    const end = start + pageSize;
    return array.slice(start, end);
}

const allItems = Array.from({length: 100}, (_, i) => `Item ${i + 1}`);
const page1 = paginate(allItems, 1, 10); // 第1页,每页10条
const page2 = paginate(allItems, 2, 10); // 第2页,每页10条

数组的扁平化

const nested = [1, [2, 3], [4, [5, 6]]];

// flat() - 扁平化数组
console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]]
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6]
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5, 6]

// flatMap() - 先map再flat
const sentences = ['Hello world', 'Good morning'];
const words = sentences.flatMap(sentence => sentence.split(' ')); // ['Hello', 'world', 'Good', 'morning']

// 实际应用:处理嵌套数据
const users = [
    { name: '张三', hobbies: ['读书', '游泳'] },
    { name: '李四', hobbies: ['跑步', '篮球', '足球'] },
    { name: '王五', hobbies: ['画画'] }
];

// 获取所有爱好
const allHobbies = users.flatMap(user => user.hobbies);
console.log(allHobbies); // ['读书', '游泳', '跑步', '篮球', '足球', '画画']

// 去重
const uniqueHobbies = [...new Set(allHobbies)];
console.log(uniqueHobbies); // ['读书', '游泳', '跑步', '篮球', '足球', '画画']

数组的迭代方法

const fruits = ['苹果', '香蕉', '橙子'];

// forEach() - 遍历数组
fruits.forEach((fruit, index) => {
    console.log(`${index}: ${fruit}`);
});

// every() - 检查所有元素是否满足条件
const allPositive = [1, 2, 3, 4, 5].every(num => num > 0); // true
const allEven = [2, 4, 6, 8, 9].every(num => num % 2 === 0); // false

// some() - 检查是否有元素满足条件
const hasEven = [1, 3, 5, 6, 7].some(num => num % 2 === 0); // true
const hasNegative = [1, 2, 3, 4, 5].some(num => num < 0); // false

// 实际应用:表单验证
const formData = {
    username: 'john_doe',
    email: 'john@example.com',
    age: 25
};

const validators = {
    username: value => value.length >= 3,
    email: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
    age: value => value >= 18 && value <= 100
};

function validateForm(data, rules) {
    const errors = {};
    
    Object.keys(rules).forEach(field => {
        if (!rules[field](data[field])) {
            errors[field] = `${field} 验证失败`;
        }
    });
    
    return {
        isValid: Object.keys(errors).length === 0,
        errors
    };
}

const result = validateForm(formData, validators);
console.log(result);

数组的高级技巧

解构赋值

const fruits = ['苹果', '香蕉', '橙子', '葡萄', '西瓜'];

// 基本解构
const [first, second, third] = fruits;
console.log(first, second, third); // '苹果' '香蕉' '橙子'

// 跳过元素
const [first2, , third2] = fruits;
console.log(first2, third2); // '苹果' '橙子'

// 剩余元素
const [first3, second3, ...rest] = fruits;
console.log(rest); // ['橙子', '葡萄', '西瓜']

// 默认值
const [a, b, c, d, e, f = '默认值'] = fruits;
console.log(f); // '默认值'

// 交换变量
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1

展开运算符

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

// 合并数组
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

// 复制数组
const copy = [...arr1]; // [1, 2, 3]

// 在特定位置插入元素
const inserted = [...arr1.slice(0, 1), 'new', ...arr1.slice(1)]; // [1, 'new', 2, 3]

// 实际应用:不可变更新
const users = [
    { id: 1, name: '张三', age: 25 },
    { id: 2, name: '李四', age: 30 }
];

// 添加新用户
const newUser = { id: 3, name: '王五', age: 28 };
const updatedUsers = [...users, newUser];

// 更新用户
const updatedUsers2 = users.map(user => 
    user.id === 1 ? { ...user, age: 26 } : user
);

// 删除用户
const filteredUsers = users.filter(user => user.id !== 1);

数组的空槽处理

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

// 检查空槽
console.log(1 in sparse); // false
console.log(0 in sparse); // true

// 处理空槽的方法
const dense = sparse.filter(() => true); // [1, 3, 5]
const dense2 = Array.from(sparse, x => x || 0); // [1, 0, 3, 0, 5]

// 实际应用:处理表单数据
const formFields = ['name', 'email', 'phone'];
const formValues = ['张三', , '13800138000']; // email字段为空

const formData = formFields.reduce((obj, field, index) => {
    if (index in formValues) {
        obj[field] = formValues[index];
    }
    return obj;
}, {});

console.log(formData); // { name: '张三', phone: '13800138000' }

性能优化技巧

避免在循环中修改数组

// 不好的做法
const numbers = [1, 2, 3, 4, 5];
for (let i = 0; i < numbers.length; i++) {
    if (numbers[i] % 2 === 0) {
        numbers.splice(i, 1); // 这会改变数组长度和索引
        i--; // 需要手动调整索引
    }
}

// 好的做法
const numbers2 = [1, 2, 3, 4, 5];
const filtered = numbers2.filter(num => num % 2 !== 0);

// 或者从后往前遍历
const numbers3 = [1, 2, 3, 4, 5];
for (let i = numbers3.length - 1; i >= 0; i--) {
    if (numbers3[i] % 2 === 0) {
        numbers3.splice(i, 1);
    }
}

使用Set进行去重

// 传统方法
function unique(arr) {
    return arr.filter((item, index) => arr.indexOf(item) === index);
}

// 使用Set(更高效)
function unique2(arr) {
    return [...new Set(arr)];
}

// 对象数组去重
const users = [
    { id: 1, name: '张三' },
    { id: 2, name: '李四' },
    { id: 1, name: '张三' }
];

function uniqueBy(arr, key) {
    const seen = new Set();
    return arr.filter(item => {
        const value = item[key];
        if (seen.has(value)) {
            return false;
        }
        seen.add(value);
        return true;
    });
}

const uniqueUsers = uniqueBy(users, 'id');

如果觉得有用就收藏点赞,有什么问题欢迎在评论区讨论!骚话王下次再见!