JavaScript数组指南:从基础操作到高阶方法
什么是数组?
数组是JavaScript中最基础也最常用的数据结构之一。数组就是一个可以存储多个值的变量,这些值在内存中拥有各自的存储位置,但共享同一个数组名称。
可以把数组想象成一个有序的容器,容器中的每个位置都有一个编号(索引),通过这个编号我们可以快速找到对应位置的值。
// 一个简单的数组示例
let fruits = ['苹果', '香蕉', '橙子', '葡萄'];
console.log(fruits); // ['苹果', '香蕉', '橙子', '葡萄']
数组的核心特性
-
索引从0开始:第一个元素的索引是0,第二个是1,依次类推
-
动态长度:数组大小可以动态变化,不需要预先指定
-
混合类型:一个数组可以包含不同类型的数据
-
引用类型:数组是对象,赋值和传递时按引用操作
创建数组的三种方式
1. 数组字面量(推荐)
这是最简单、最直观的创建方式,使用方括号 [] 包裹元素:
// 空数组
let emptyArray = [];
// 包含数字的数组
let numbers = [10, 20, 30, 40, 50];
// 包含字符串的数组
let students = ['张三', '李四', '王五', '赵六'];
// 混合类型数组
let mixed = ['张三', 25, true, { city: '北京' }, [1, 2, 3]];
console.log(numbers[0]); // 10
console.log(students[1]); // 李四
个人经验:日常开发中,我几乎都用数组字面量创建数组,代码简洁明了,性能也很好。
2. 使用new关键字
// 创建空数组
let fruits = new Array();
// 创建指定长度的数组
let fixedLength = new Array(5); // 创建一个长度为5的空数组
console.log(fixedLength); // [empty × 5]
// 创建包含元素的数组
let colors = new Array('红色', '蓝色', '绿色', '黄颜色');
console.log(colors); // ['红色', '蓝色', '绿色', '黄颜色']
// 遍历数组
for (let i = 0; i < colors.length; i++) {
console.log(colors[i]);
}
3. 使用Array构造函数
// 直接传入元素
let languages = new Array('JavaScript', 'Python', 'Java', 'C++');
console.log(languages); // ['JavaScript', 'Python', 'Java', 'C++']
// 使用of方法(ES6)
let digits = Array.of(1, 2, 3, 4, 5);
console.log(digits); // [1, 2, 3, 4, 5]
// 从类数组对象创建
let str = 'Hello';
let charArray = Array.from(str);
console.log(charArray); // ['H', 'e', 'l', 'l', 'o']
数组的基本操作
访问数组元素
let books = ['JavaScript高级程序设计', '你不知道的JS', 'ES6入门指南', '算法图解'];
// 通过索引访问
console.log(books[0]); // JavaScript高级程序设计
console.log(books[2]); // ES6入门指南
// 访问第一个元素
console.log(books[0]); // JavaScript高级程序设计
// 访问之后一个元素
console.log(books[books.length - 1]); // 算法图解
// 使用at方法(ES2022)
console.log(books.at(-1)); // 算法图解(更优雅的写法)
修改数组元素
let scores = [85, 92, 78, 90, 88];
console.log('原始数组:', scores);
// 修改指定索引的值
scores[2] = 95;
console.log('修改后:', scores); // [85, 92, 95, 90, 88]
// 批量修改
for (let i = 0; i < scores.length; i++) {
if (scores[i] < 90) {
scores[i] += 5; // 给低于90分的加5分
}
}
console.log('加分后:', scores);
添加元素
let fruits = ['苹果', '香蕉'];
console.log('初始数组:', fruits);
// push() - 在末尾添加一个或多个元素
fruits.push('橙子');
console.log('push后:', fruits); // ['苹果', '香蕉', '橙子']
fruits.push('葡萄', '西瓜');
console.log('push多个:', fruits); // ['苹果', '香蕉', '橙子', '葡萄', '西瓜']
// unshift() - 在开头添加一个或多个元素
fruits.unshift('草莓');
console.log('unshift后:', fruits); // ['草莓', '苹果', '香蕉', '橙子', '葡萄', '西瓜']
// 通过索引添加(如果索引大于当前长度)
fruits[10] = '芒果';
console.log('通过索引添加:', fruits); // 中间会有空位
删除元素
let fruits = ['苹果', '香蕉', '橙子', '葡萄', '西瓜'];
console.log('初始数组:', fruits);
// pop() - 删除之后一个元素
let lastFruit = fruits.pop();
console.log('删除的元素:', lastFruit); // 西瓜
console.log('pop后:', fruits); // ['苹果', '香蕉', '橙子', '葡萄']
// shift() - 删除第一个元素
let firstFruit = fruits.shift();
console.log('删除的元素:', firstFruit); // 苹果
console.log('shift后:', fruits); // ['香蕉', '橙子', '葡萄']
// splice() - 删除指定位置的元素
let removed = fruits.splice(1, 1); // 从索引1开始删除1个元素
console.log('删除的元素:', removed); // ['橙子']
console.log('splice后:', fruits); // ['香蕉', '葡萄']
常用数组方法详解
1. concat() - 合并数组
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
// 合并两个数组
const combined = arr1.concat(arr2);
console.log(combined); // [1, 2, 3, 4, 5, 6]
// 合并多个数组
const all = arr1.concat(arr2, arr3);
console.log(all); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 合并数组和单个值
const withValue = arr1.concat(10, 11);
console.log(withValue); // [1, 2, 3, 10, 11]
// 现在写法(使用展开运算符)
const modernCombined = [...arr1, ...arr2];
console.log(modernCombined); // [1, 2, 3, 4, 5, 6]
2. slice() - 提取子数组
const numbers = [10, 20, 30, 40, 50, 60, 70];
// slice(start, end) - 从start到end(不包括end)
const sub1 = numbers.slice(2, 5);
console.log(sub1); // [30, 40, 50]
// 只指定起始位置
const sub2 = numbers.slice(3);
console.log(sub2); // [40, 50, 60, 70]
// 负数索引
const sub3 = numbers.slice(-3);
console.log(sub3); // [50, 60, 70]
// 复制整个数组
const copy = numbers.slice();
console.log(copy); // [10, 20, 30, 40, 50, 60, 70]
3. splice() - 多功能操作
let months = ['一月', '二月', '三月', '四月', '五月', '六月'];
console.log('原始数组:', months);
// 删除元素
let removed = months.splice(2, 2); // 从索引2删除2个元素
console.log('删除的元素:', removed); // ['三月', '四月']
console.log('删除后:', months); // ['一月', '二月', '五月', '六月']
// 插入元素
months.splice(2, 0, '三月', '四月'); // 在索引2插入,不删除
console.log('插入后:', months); // ['一月', '二月', '三月', '四月', '五月', '六月']
// 替换元素
months.splice(3, 1, '四月(修订版)');
console.log('替换后:', months); // ['一月', '二月', '三月', '四月(修订版)', '五月', '六月']
4. forEach() - 遍历数组
let users = [
{ name: '张三', age: 25 },
{ name: '李四', age: 30 },
{ name: '王五', age: 28 },
{ name: '赵六', age: 22 }
];
// 基本遍历
users.forEach(function(user, index) {
console.log(`索引${index}: ${user.name},年龄${user.age}`);
});
// 使用箭头函数
users.forEach((user, index) => {
console.log(`${user.name}明年就${user.age + 1}岁了`);
});
// 计算平均年龄
let totalAge = 0;
users.forEach(user => {
totalAge += user.age;
});
console.log('平均年龄:', totalAge / users.length);
5. map() - 映射新数组
const numbers = [1, 2, 3, 4, 5];
// 每个元素乘以2
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// 提取对象属性
const products = [
{ id: 1, name: '笔记本电脑', price: 5999 },
{ id: 2, name: '手机', price: 3299 },
{ id: 3, name: '平板', price: 2499 }
];
const productNames = products.map(product => product.name);
console.log(productNames); // ['笔记本电脑', '手机', '平板']
// 格式化数据
const formattedProducts = products.map(product => ({
...product,
price: `¥${product.price}`,
inStock: true
}));
console.log(formattedProducts);
6. filter() - 过滤数组
const numbers = [12, 5, 8, 130, 44, 9, 33];
// 过滤出大于10的数
const filtered = numbers.filter(num => num > 10);
console.log(filtered); // [12, 130, 44, 33]
// 过滤出偶数
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [12, 8, 130, 44]
// 实际应用:筛选符合条件的用户
const users = [
{ name: '张三', age: 25, active: true },
{ name: '李四', age: 17, active: true },
{ name: '王五', age: 30, active: false },
{ name: '赵六', age: 19, active: true }
];
const actives = users.filter(user => user.active && user.age >= 18);
console.log(actives);
// [{ name: '张三', age: 25, active: true }, { name: '赵六', age: 19, active: true }]
7. reduce() - 归并计算
const numbers = [15, 25, 35, 45, 55];
// 计算总和
const sum = numbers.reduce((accumulator, currentValue) => {
return accumulator + currentValue;
}, 0);
console.log('总和:', sum); // 175
// 计算乘积
const product = numbers.reduce((acc, val) => acc * val, 1);
console.log('乘积:', product); // 15*25*35*45*55 = 32484375
// 高级应用:统计出现次数
const fruits = ['苹果', '香蕉', '苹果', '橙子', '香蕉', '苹果', '葡萄'];
const fruitCount = fruits.reduce((count, fruit) => {
count[fruit] = (count[fruit] || 0) + 1;
return count;
}, {});
console.log(fruitCount); // {苹果: 3, 香蕉: 2, 橙子: 1, 葡萄: 1}
8. find() 和 findIndex()
const users = [
{ id: 101, name: '张三', role: 'admin' },
{ id: 102, name: '李四', role: 'user' },
{ id: 103, name: '王五', role: 'user' },
{ id: 104, name: '赵六', role: 'moderator' }
];
// find() - 查找第一个匹配的元素
const admin = users.find(user => user.role === 'admin');
console.log(admin); // { id: 101, name: '张三', role: 'admin' }
// findIndex() - 查找第一个匹配的索引
const userIndex = users.findIndex(user => user.name === '王五');
console.log(userIndex); // 2
// 如果找不到,find返回undefined,findIndex返回-1
const notFound = users.find(user => user.name === '刘八');
console.log(notFound); // undefined
9. some() 和 every()
const scores = [85, 90, 78, 92, 88];
// some() - 至少有一个满足条件
const hasHighScore = scores.some(score => score >= 90);
console.log('是否有90分以上?', hasHighScore); // true
const hasPerfect = scores.some(score => score === 100);
console.log('是否有满分?', hasPerfect); // false
// every() - 所有元素都满足条件
const allPass = scores.every(score => score >= 60);
console.log('是否都及格?', allPass); // true
const allExcellent = scores.every(score => score >= 90);
console.log('是否都优秀?', allExcellent); // false
10. includes() 和 indexOf()
const colors = ['红色', '蓝色', '绿色', '黄颜色', '蓝色'];
// includes() - 检查是否包含
console.log(colors.includes('绿色')); // true
console.log(colors.includes('紫色')); // false
// indexOf() - 查找第一次出现的索引
console.log(colors.indexOf('蓝色')); // 1
console.log(colors.indexOf('蓝色', 2)); // 4(从索引2开始查找)
// lastIndexOf() - 查找之后一次出现的索引
console.log(colors.lastIndexOf('蓝色')); // 4
console.log(colors.lastIndexOf('紫色')); // -1
11. sort() - 排序
// 字符串排序
const fruits = ['香蕉', '苹果', '橙子', '葡萄', '草莓'];
fruits.sort();
console.log(fruits); // ['苹果', '葡萄', '橙子', '草莓', '香蕉']
// 数字排序(需要提供比较函数)
const numbers = [45, 3, 27, 12, 8, 36, 19];
// 默认排序(按字符串排序,结果不符合预期)
numbers.sort();
console.log(numbers); // [12, 19, 27, 3, 36, 45, 8]
// 正确的数字升序排序
numbers.sort((a, b) => a - b);
console.log('升序:', numbers); // [3, 8, 12, 19, 27, 36, 45]
// 降序排序
numbers.sort((a, b) => b - a);
console.log('降序:', numbers); // [45, 36, 27, 19, 12, 8, 3]
// 对象数组排序
const students = [
{ name: '张三', score: 85 },
{ name: '李四', score: 92 },
{ name: '王五', score: 78 },
{ name: '赵六', score: 96 }
];
// 按分数降序
students.sort((a, b) => b.score - a.score);
console.log(students);
12. reverse() - 反转数组
const letters = ['A', 'B', 'C', 'D', 'E'];
console.log('原始数组:', letters);
letters.reverse();
console.log('反转后:', letters); // ['E', 'D', 'C', 'B', 'A']
// 实际应用:获取数组的之后几个元素(不修改原数组)
const numbers = [10, 20, 30, 40, 50, 60];
const lastThree = [...numbers].reverse().slice(0, 3).reverse();
console.log(lastThree); // [40, 50, 60]
13. join() - 数组转字符串
const words = ['Hello', 'World', 'JavaScript', 'is', 'awesome'];
// 默认用逗号连接
console.log(words.join()); // 'Hello,World,JavaScript,is,awesome'
// 指定连接符
console.log(words.join(' ')); // 'Hello World JavaScript is awesome'
console.log(words.join('-')); // 'Hello-World-JavaScript-is-awesome'
console.log(words.join('')); // 'HelloWorldJavaScriptisawesome'
// 实际应用:URL参数拼接
const params = {
page: 1,
limit: 10,
keyword: 'javascript'
};
const queryString = Object.entries(params)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&');
console.log(queryString); // 'page=1&limit=10&keyword=javascript'
14. flat() 和 flatMap()
// flat() - 扁平化数组
const nestedArray = [1, [2, 3], [4, [5, 6]]];
// 默认扁平化一层
const flat1 = nestedArray.flat();
console.log(flat1); // [1, 2, 3, 4, [5, 6]]
// 指定扁平化深度
const flat2 = nestedArray.flat(2);
console.log(flat2); // [1, 2, 3, 4, 5, 6]
// flatMap() - 映射并扁平化
const sentences = ['Hello world', 'JavaScript is fun', 'I love coding'];
const words = sentences.flatMap(sentence => sentence.split(' '));
console.log(words);
// ['Hello', 'world', 'JavaScript', 'is', 'fun', 'I', 'love', 'coding']
15. 静态方法:Array.isArray()
console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray({})); // false
console.log(Array.isArray('hello')); // false
console.log(Array.isArray(null)); // false
console.log(Array.isArray(undefined)); // false
// 实际应用:安全的数组操作
function processData(data) {
if (!Array.isArray(data)) {
console.log('输入不是数组,转为数组');
data = [data];
}
return data.map(item => item * 2);
}
console.log(processData([1, 2, 3])); // [2, 4, 6]
console.log(processData(5)); // [10](自动转为数组)
数组遍历方式对比
const fruits = ['苹果', '香蕉', '橙子', '葡萄'];
// 1. for循环(性能很好)
console.log('=== for循环 ===');
for (let i = 0; i < fruits.length; i++) {
console.log(`索引${i}: ${fruits[i]}`);
}
// 2. for...of(遍历值)
console.log('=== for...of ===');
for (let fruit of fruits) {
console.log(fruit);
}
// 3. for...in(遍历索引,不推荐用于数组)
console.log('=== for...in ===');
for (let index in fruits) {
console.log(`索引${index}: ${fruits[index]}`);
}
// 4. forEach(函数式)
console.log('=== forEach ===');
fruits.forEach((fruit, index) => {
console.log(`索引${index}: ${fruit}`);
});
// 5. map(返回新数组)
console.log('=== map ===');
const upperFruits = fruits.map(fruit => fruit.toUpperCase());
console.log(upperFruits);
课程知识要点
-
数组定义:有序的数据,通过索引访问,索引从0开始
-
创建方式:字面量[]、new Array()、Array.of()、Array.from()
-
增删操作:push/pop(末尾)、unshift/shift(开头)、splice(任意位置)
-
遍历方法:for、forEach、for...of、map、filter
-
查找方法:indexOf、lastIndexOf、find、findIndex、includes
-
判断方法:some、every、isArray
-
转换方法:map、filter、reduce、flat、flatMap
-
排序方法:sort、reverse
-
合并拆分:concat、slice、join
-
静态方法:Array.isArray()、Array.from()、Array.of()
开发实践建议
基于多年JavaScript开发经验,我对数组使用的建议如下:
常用组合模式:
// 链式调用示例
const products = [
{ name: '笔记本', price: 5999, category: '电子产品' },
{ name: 'T恤', price: 99, category: '服装' },
{ name: '手机', price: 3299, category: '电子产品' },
{ name: '裤子', price: 199, category: '服装' },
{ name: '平板', price: 2499, category: '电子产品' }
];
// 获取所有电子产品的名称,按价格排序
const electronicsNames = products
.filter(product => product.category === '电子产品')
.sort((a, b) => a.price - b.price)
.map(product => product.name);
console.log(electronicsNames); // ['平板', '手机', '笔记本']
性能考虑:
-
大量数据时,传统for循环性能较优
-
函数式方法(map/filter/reduce)代码更清晰,适合大多数场景
-
避免在循环中修改数组长度
常见:
// 1. 数组是引用类型
let arr1 = [1, 2, 3];
let arr2 = arr1; // 不是复制,是引用
arr2.push(4);
console.log(arr1); // [1, 2, 3, 4](原数组被修改)
// 正确复制方式
let arr3 = [...arr1];
let arr4 = arr1.slice();
// 2. 使用delete删除元素(留下空位)
let colors = ['红', '绿', '蓝'];
delete colors[1];
console.log(colors); // ['红', empty, '蓝']
console.log(colors.length); // 3(仍然3)
// 正确方式:使用splice
colors.splice(1, 1);
console.log(colors); // ['红', '蓝']
// 3. sort默认按字符串排序
let nums = [10, 2, 30, 4];
nums.sort();
console.log(nums); // [10, 2, 30, 4](不是数值排序)
// 正确方式:提供比较函数
nums.sort((a, b) => a - b);
console.log(nums); // [2, 4, 10, 30]
数组是JavaScript中最灵活、强大的数据结构之一。掌握这些方法和技巧,能够让你在处理数据时更加得心应手,写出更简洁高效的代码。