====== 第六章 数组 ======
===== 6.1 数组概述 =====
数组是JavaScript中最重要的数据结构之一,用于存储有序的元素集合。JavaScript数组是**动态的**、**异构的**,可以存储任意类型的元素。
===== 6.2 创建数组 =====
==== 6.2.1 数组字面量 ====
// 空数组
const empty = [];
// 带有元素的数组
const numbers = [1, 2, 3, 4, 5];
const mixed = [1, "two", true, null, { a: 1 }, [1, 2]];
// 多行格式
const fruits = [
"apple",
"banana",
"orange",
"grape"
];
// 数组长度
console.log(numbers.length); // 5
==== 6.2.2 Array构造函数 ====
// 使用Array构造函数(不推荐)
const arr1 = new Array(); // []
const arr2 = new Array(5); // [empty × 5] - 5个空槽
const arr3 = new Array(1, 2, 3); // [1, 2, 3]
// 注意:单参数时会被视为长度
new Array(3); // [empty, empty, empty]
new Array("3"); // ["3"]
// Array.of()(ES6)- 更安全的构造函数
Array.of(3); // [3]
Array.of(1, 2, 3); // [1, 2, 3]
==== 6.2.3 Array.from()(ES6) ====
// 从类数组对象创建数组
const fromString = Array.from("hello"); // ["h", "e", "l", "l", "o"]
// 从Set创建
const fromSet = Array.from(new Set([1, 2, 3])); // [1, 2, 3]
// 从Map创建
const fromMap = Array.from(new Map([[1, "a"], [2, "b"]])); // [[1, "a"], [2, "b"]]
// 带映射函数
const doubled = Array.from([1, 2, 3], x => x * 2); // [2, 4, 6]
// 创建序列
const range = Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]
===== 6.3 数组基础操作 =====
==== 6.3.1 访问和修改元素 ====
const arr = ["a", "b", "c"];
// 访问元素
arr[0]; // "a"
arr[1]; // "b"
arr[arr.length - 1]; // "c"(最后一个)
// 修改元素
arr[0] = "A";
console.log(arr); // ["A", "b", "c"]
// 添加元素(索引超出当前长度)
arr[5] = "f";
console.log(arr); // ["A", "b", "c", empty, empty, "f"]
// 负数索引(ES2022)
arr.at(-1); // "f"(最后一个元素)
arr.at(-2); // undefined(empty槽)
==== 6.3.2 添加和删除元素 ====
const arr = [1, 2, 3];
// push - 末尾添加,返回新长度
arr.push(4); // 4(新长度)
arr.push(5, 6); // 6(可添加多个)
// pop - 末尾删除,返回被删除的元素
arr.pop(); // 6
// unshift - 开头添加,返回新长度
arr.unshift(0); // 5
arr.unshift(-1, -2); // 7
// shift - 开头删除,返回被删除的元素
arr.shift(); // -1
// splice - 任意位置添加/删除
const colors = ["red", "green", "blue"];
// 删除
colors.splice(1, 1); // ["green"](从索引1删除1个)
// colors现在是 ["red", "blue"]
// 添加
colors.splice(1, 0, "yellow"); // [](从索引1删除0个,添加"yellow")
// colors现在是 ["red", "yellow", "blue"]
// 替换
colors.splice(1, 1, "green", "purple"); // ["yellow"]
// colors现在是 ["red", "green", "purple", "blue"]
===== 6.4 数组方法大全 =====
==== 6.4.1 查找方法 ====
const arr = [5, 12, 8, 130, 44];
// indexOf - 查找元素索引(严格相等)
arr.indexOf(8); // 2
arr.indexOf(100); // -1(不存在)
arr.indexOf(5, 1); // -1(从索引1开始查找)
// lastIndexOf - 从后向前查找
arr.lastIndexOf(44); // 4
// includes - 检查是否包含(ES2016)
arr.includes(8); // true
arr.includes(100); // false
// find - 返回第一个满足条件的元素(ES6)
arr.find(x => x > 10); // 12
arr.find(x => x > 200); // undefined
// findIndex - 返回第一个满足条件的索引(ES6)
arr.findIndex(x => x > 10); // 1
arr.findIndex(x => x > 200); // -1
// findLast / findLastIndex(ES2023)
arr.findLast(x => x > 10); // 44
arr.findLastIndex(x => x > 10); // 4
==== 6.4.2 遍历方法 ====
const arr = ["a", "b", "c"];
// forEach - 遍历每个元素
arr.forEach((item, index, array) => {
console.log(`${index}: ${item}`);
});
// map - 映射,返回新数组
const doubled = [1, 2, 3].map(x => x * 2); // [2, 4, 6]
// filter - 过滤,返回满足条件的元素
const evens = [1, 2, 3, 4, 5].filter(x => x % 2 === 0); // [2, 4]
// reduce - 归约
const sum = [1, 2, 3, 4].reduce((acc, x) => acc + x, 0); // 10
// reduceRight - 从右向左归约
const flattened = [[0, 1], [2, 3], [4, 5]].reduceRight(
(a, b) => a.concat(b), []
); // [4, 5, 2, 3, 0, 1]
// every - 是否所有元素都满足条件
[1, 2, 3].every(x => x > 0); // true
// some - 是否有元素满足条件
[1, 2, 3].some(x => x > 2); // true
**reduce的高级用法**:
// 分组
const people = [
{ name: "Alice", age: 21 },
{ name: "Bob", age: 25 },
{ name: "Carol", age: 21 }
];
const groupedByAge = people.reduce((acc, person) => {
const key = person.age;
if (!acc[key]) acc[key] = [];
acc[key].push(person);
return acc;
}, {});
// { 21: [{Alice}, {Carol}], 25: [{Bob}] }
// 统计
const fruits = ["apple", "banana", "apple", "orange", "banana", "apple"];
const count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
// { apple: 3, banana: 2, orange: 1 }
// 管道
const pipeline = (...fns) => x => fns.reduce((v, f) => f(v), x);
==== 6.4.3 排序和反转 ====
// sort - 原地排序(默认按字符串Unicode)
const arr = ["banana", "apple", "cherry"];
arr.sort(); // ["apple", "banana", "cherry"]
// 数字排序需要比较函数
const nums = [10, 2, 30, 1];
nums.sort(); // [1, 10, 2, 30](错误!)
nums.sort((a, b) => a - b); // [1, 2, 10, 30](升序)
nums.sort((a, b) => b - a); // [30, 10, 2, 1](降序)
// 对象排序
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 20 }
];
users.sort((a, b) => a.age - b.age);
// 字符串排序(localeCompare)
["café", "cafe", "calzone"].sort((a, b) => a.localeCompare(b));
// toSorted(ES2023)- 不修改原数组
const original = [3, 1, 2];
const sorted = original.toSorted((a, b) => a - b);
console.log(original); // [3, 1, 2]
console.log(sorted); // [1, 2, 3]
// reverse - 原地反转
[1, 2, 3].reverse(); // [3, 2, 1]
// toReversed(ES2023)- 不修改原数组
[1, 2, 3].toReversed(); // [3, 2, 1]
==== 6.4.4 截取和连接 ====
const arr = [1, 2, 3, 4, 5];
// slice - 截取(不修改原数组)
arr.slice(1, 3); // [2, 3](从索引1到3,不包括3)
arr.slice(2); // [3, 4, 5](从索引2到末尾)
arr.slice(-2); // [4, 5](最后两个)
arr.slice(); // [1, 2, 3, 4, 5](浅拷贝)
// concat - 连接数组
[1, 2].concat([3, 4]); // [1, 2, 3, 4]
[1, 2].concat(3, 4); // [1, 2, 3, 4]
[1, 2].concat([3], [4, 5]); // [1, 2, 3, 4, 5]
// 使用展开运算符(推荐)
[...arr1, ...arr2, item];
==== 6.4.5 填充和复制 ====
// fill - 填充
const arr1 = new Array(5).fill(0); // [0, 0, 0, 0, 0]
[1, 2, 3, 4, 5].fill(0, 2, 4); // [1, 2, 0, 0, 5]
// copyWithin - 复制数组的一部分到另一部分
[1, 2, 3, 4, 5].copyWithin(0, 3); // [4, 5, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(1, 3, 4); // [1, 4, 3, 4, 5]
===== 6.5 ES6+数组新特性 =====
==== 6.5.1 展开运算符 ====
// 展开数组
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
// 复制数组(浅拷贝)
const copy = [...arr1];
// 合并数组
const merged = [...arr1, ...arr2];
// 函数调用时展开
const nums = [1, 2, 3];
Math.max(...nums); // 3(等价于 Math.max(1, 2, 3))
==== 6.5.2 解构赋值 ====
// 数组解构
const [a, b, c] = [1, 2, 3]; // a=1, b=2, c=3
// 跳过元素
const [first, , third] = [1, 2, 3]; // first=1, third=3
// 剩余元素
const [head, ...tail] = [1, 2, 3, 4]; // head=1, tail=[2, 3, 4]
// 默认值
const [x = 0, y = 0] = [1]; // x=1, y=0
// 嵌套解构
const [a, [b, c]] = [1, [2, 3]]; // a=1, b=2, c=3
// 交换变量
let m = 1, n = 2;
[m, n] = [n, m]; // m=2, n=1
==== 6.5.3 Array新方法(ES2022+) ====
const arr = [1, 2, 3, 4, 5];
// at() - 支持负数索引
arr.at(0); // 1
arr.at(-1); // 5(最后一个)
arr.at(-2); // 4(倒数第二个)
// 不可变方法(ES2023)- 返回新数组
arr.toReversed(); // [5, 4, 3, 2, 1]
arr.toSorted((a, b) => b - a); // [5, 4, 3, 2, 1]
arr.toSpliced(1, 2, "a", "b"); // [1, "a", "b", 4, 5]
arr.with(2, "three"); // [1, 2, "three", 4, 5]
// 原数组保持不变
console.log(arr); // [1, 2, 3, 4, 5]
===== 6.6 数组高级操作 =====
==== 6.6.1 扁平化数组 ====
const nested = [1, [2, 3], [4, [5, 6]]];
// flat() - 扁平化(ES2019)
nested.flat(); // [1, 2, 3, 4, [5, 6]](默认深度1)
nested.flat(2); // [1, 2, 3, 4, 5, 6]
nested.flat(Infinity); // 完全扁平化
// flatMap() = map() + flat(1)
const sentences = ["Hello world", "How are you"];
const words = sentences.flatMap(s => s.split(" "));
// ["Hello", "world", "How", "are", "you"]
==== 6.6.2 分组(ES2024) ====
// group() / groupToMap()(ES2024)
const inventory = [
{ name: "apple", type: "fruit", quantity: 5 },
{ name: "carrot", type: "vegetable", quantity: 3 },
{ name: "banana", type: "fruit", quantity: 2 }
];
// 按类型分组
const byType = Object.groupBy(inventory, item => item.type);
// {
// fruit: [{apple}, {banana}],
// vegetable: [{carrot}]
// }
// 按数量分组
const byQuantity = Object.groupBy(inventory,
item => item.quantity > 3 ? "enough" : "restock"
);
==== 6.6.3 数组去重 ====
const arr = [1, 2, 2, 3, 3, 3, 4];
// 使用Set(最简单)
[...new Set(arr)]; // [1, 2, 3, 4]
Array.from(new Set(arr)); // [1, 2, 3, 4]
// 使用filter
arr.filter((item, index) => arr.indexOf(item) === index);
// 对象数组去重
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Alice" }
];
const unique = users.filter((user, index, self) =>
index === self.findIndex(u => u.id === user.id)
);
===== 6.7 类型化数组(TypedArray) =====
类型化数组用于处理二进制数据,在WebGL、文件操作、网络通信等场景中非常有用。
// 创建ArrayBuffer
const buffer = new ArrayBuffer(16); // 16字节的内存空间
// 创建视图
const int32View = new Int32Array(buffer);
int32View[0] = 42;
console.log(int32View[0]); // 42
// 直接创建类型化数组
const int8 = new Int8Array(4); // 4个8位整数
const uint8 = new Uint8Array(4); // 4个无符号8位整数
const int16 = new Int16Array(4); // 4个16位整数
const uint16 = new Uint16Array(4); // 4个无符号16位整数
const int32 = new Int32Array(4); // 4个32位整数
const uint32 = new Uint32Array(4); // 4个无符号32位整数
const float32 = new Float32Array(4); // 4个32位浮点数
const float64 = new Float64Array(4); // 4个64位浮点数
const bigInt64 = new BigInt64Array(4); // 4个64位大整数
// 从数组创建
const arr = new Int32Array([1, 2, 3, 4]);
// 类型化数组方法(类似普通数组)
arr.forEach(x => console.log(x));
const doubled = arr.map(x => x * 2);
const sum = arr.reduce((a, b) => a + b, 0);
// 设置和获取
arr.set([10, 20], 1); // 从索引1开始设置值
const sub = arr.subarray(1, 3); // 创建子视图
===== 6.8 练习题 =====
==== 练习1:数组基础 ====
// 1. 实现一个函数chunk(arr, size),将数组分割成指定大小的小数组
// chunk([1,2,3,4,5,6], 2) => [[1,2], [3,4], [5,6]]
// 2. 实现一个函数compact(arr),移除数组中所有假值
// compact([0, 1, false, 2, '', 3]) => [1, 2, 3]
// 3. 实现一个函数difference(arr1, arr2),返回在arr1中但不在arr2中的元素
// difference([1,2,3], [2,3,4]) => [1]
==== 练习2:数组方法 ====
// 1. 使用reduce实现map、filter、find
// 2. 实现一个函数flattenDeep(arr),深度扁平化数组
// flattenDeep([1, [2, [3, [4]]]]) => [1, 2, 3, 4]
// 3. 实现一个函数zip(...arrays),将多个数组合并
// zip([1,2], ['a','b'], [true,false]) => [[1,'a',true], [2,'b',false]]
==== 练习3:综合应用 ====
// 1. 给定一个用户数组,按年龄段分组(18-25,26-35,36+)
// 2. 实现一个函数,找出数组中出现次数最多的元素
// 3. 实现一个函数,对对象数组进行多字段排序
// sortBy(users, ['age', 'name']) // 先按age排序,age相同按name排序
===== 参考答案 =====
**练习1答案**:
// 1. chunk
function chunk(arr, size) {
const result = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
// 或使用reduce
const chunk = (arr, size) =>
arr.reduce((acc, _, i) =>
i % size === 0 ? [...acc, arr.slice(i, i + size)] : acc,
[]);
// 2. compact
function compact(arr) {
return arr.filter(Boolean);
}
// 3. difference
function difference(arr1, arr2) {
const set2 = new Set(arr2);
return arr1.filter(item => !set2.has(item));
}
**练习2答案**:
// 1. 用reduce实现map/filter/find
const map = (arr, fn) => arr.reduce((acc, item, i) => [...acc, fn(item, i, arr)], []);
const filter = (arr, fn) => arr.reduce((acc, item) => fn(item) ? [...acc, item] : acc, []);
const find = (arr, fn) => arr.reduce((acc, item) => acc === undefined && fn(item) ? item : acc, undefined);
// 2. flattenDeep
function flattenDeep(arr) {
return arr.reduce((acc, item) =>
acc.concat(Array.isArray(item) ? flattenDeep(item) : item),
[]);
}
// 或使用flat
const flattenDeep = arr => arr.flat(Infinity);
// 3. zip
function zip(...arrays) {
const minLength = Math.min(...arrays.map(arr => arr.length));
return Array.from({ length: minLength }, (_, i) =>
arrays.map(arr => arr[i])
);
}
**练习3答案**:
// 1. 按年龄段分组
function groupByAgeRange(users) {
return Object.groupBy(users, user => {
if (user.age <= 25) return '18-25';
if (user.age <= 35) return '26-35';
return '36+';
});
}
// 2. 出现次数最多的元素
function mostFrequent(arr) {
const count = arr.reduce((acc, item) => {
acc[item] = (acc[item] || 0) + 1;
return acc;
}, {});
return Object.entries(count)
.sort((a, b) => b[1] - a[1])[0][0];
}
// 3. 多字段排序
function sortBy(arr, keys) {
return [...arr].sort((a, b) => {
for (let key of keys) {
if (a[key] < b[key]) return -1;
if (a[key] > b[key]) return 1;
}
return 0;
});
}
===== 本章小结 =====
本章全面学习了JavaScript数组:
* 数组的创建和基础操作
* 丰富的数组方法(查找、遍历、排序、截取)
* ES6+新特性(展开运算符、解构、新方法)
* 高级操作(扁平化、分组、去重)
* 类型化数组简介
数组是日常开发中最常用的数据结构,熟练掌握数组操作能大大提高开发效率。
下一章将学习字符串和正则表达式。