JavaScript 教程

纯干货教学,从零开始学习 JavaScript

JavaScript 数组

数组是 JavaScript 中用于存储多个值的集合。它是一种特殊的对象,可以存储任意类型的数据,并且可以动态调整大小。

🎯 数组的重要性

数组是 JavaScript 编程中最常用的数据结构之一,它允许我们组织和操作大量数据。掌握数组的使用方法是成为一名优秀 JavaScript 开发者的关键。

什么是数组?

数组是一种有序的集合,可以存储多个值。数组中的每个值都有一个索引,从 0 开始。

创建数组的方法

1. 数组字面量

// 使用数组字面量创建数组
const fruits = ["苹果", "香蕉", "橙子", "葡萄"];
const numbers = [1, 2, 3, 4, 5];
const mixed = ["字符串", 123, true, null, undefined];

console.log(fruits); // 输出: ["苹果", "香蕉", "橙子", "葡萄"]
console.log(numbers); // 输出: [1, 2, 3, 4, 5]
console.log(mixed); // 输出: ["字符串", 123, true, null, undefined]

2. Array 构造函数

// 使用 Array 构造函数创建数组
const fruits = new Array("苹果", "香蕉", "橙子", "葡萄");
const numbers = new Array(1, 2, 3, 4, 5);

// 创建指定长度的空数组
const emptyArray = new Array(5);
console.log(emptyArray); // 输出: [empty × 5]

console.log(fruits); // 输出: ["苹果", "香蕉", "橙子", "葡萄"]
console.log(numbers); // 输出: [1, 2, 3, 4, 5]

3. Array.from() 方法

// 从类数组对象创建数组
const arrayLike = { 0: "苹果", 1: "香蕉", 2: "橙子", length: 3 };
const fruits = Array.from(arrayLike);
console.log(fruits); // 输出: ["苹果", "香蕉", "橙子"]

// 从字符串创建数组
const str = "hello";
const chars = Array.from(str);
console.log(chars); // 输出: ["h", "e", "l", "l", "o"]

// 从 Set 创建数组
const set = new Set([1, 2, 3, 4, 5]);
const numbers = Array.from(set);
console.log(numbers); // 输出: [1, 2, 3, 4, 5]

// 使用映射函数
const doubled = Array.from([1, 2, 3], x => x * 2);
console.log(doubled); // 输出: [2, 4, 6]

4. Array.of() 方法

// 使用 Array.of() 创建数组
const fruits = Array.of("苹果", "香蕉", "橙子", "葡萄");
const numbers = Array.of(1, 2, 3, 4, 5);
const singleElement = Array.of(5);

console.log(fruits); // 输出: ["苹果", "香蕉", "橙子", "葡萄"]
console.log(numbers); // 输出: [1, 2, 3, 4, 5]
console.log(singleElement); // 输出: [5](注意:与 new Array(5) 不同)

访问数组元素

通过索引访问数组元素:

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

// 访问元素
console.log(fruits[0]); // 输出: 苹果
console.log(fruits[1]); // 输出: 香蕉
console.log(fruits[2]); // 输出: 橙子
console.log(fruits[3]); // 输出: 葡萄

// 访问不存在的元素
console.log(fruits[4]); // 输出: undefined

// 修改元素
fruits[1] = "梨";
console.log(fruits); // 输出: ["苹果", "梨", "橙子", "葡萄"]

数组长度

使用 length 属性获取或设置数组的长度:

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

// 获取数组长度
console.log(fruits.length); // 输出: 4

// 设置数组长度(截断数组)
fruits.length = 2;
console.log(fruits); // 输出: ["苹果", "香蕉"]

// 设置数组长度(扩展数组)
fruits.length = 5;
console.log(fruits); // 输出: ["苹果", "香蕉", empty × 3]

数组遍历

1. for 循环

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

// 使用 for 循环遍历数组
for (let i = 0; i < fruits.length; i++) {
    console.log(fruits[i]);
}
// 输出:
// 苹果
// 香蕉
// 橙子
// 葡萄

2. for...of 循环

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

// 使用 for...of 循环遍历数组
for (const fruit of fruits) {
    console.log(fruit);
}
// 输出:
// 苹果
// 香蕉
// 橙子
// 葡萄

// 同时获取索引和值
for (const [index, fruit] of fruits.entries()) {
    console.log(`${index}: ${fruit}`);
}
// 输出:
// 0: 苹果
// 1: 香蕉
// 2: 橙子
// 3: 葡萄

3. forEach() 方法

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

// 使用 forEach() 方法遍历数组
fruits.forEach(function(fruit, index, array) {
    console.log(`${index}: ${fruit}`);
});
// 输出:
// 0: 苹果
// 1: 香蕉
// 2: 橙子
// 3: 葡萄

// 使用箭头函数
fruits.forEach((fruit, index) => {
    console.log(`${index}: ${fruit}`);
});

4. map() 方法

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

// 使用 map() 方法创建新数组
const doubled = numbers.map(function(num) {
    return num * 2;
});

console.log(doubled); // 输出: [2, 4, 6, 8, 10]
console.log(numbers); // 输出: [1, 2, 3, 4, 5](原数组不变)

// 使用箭头函数
const squared = numbers.map(num => num * num);
console.log(squared); // 输出: [1, 4, 9, 16, 25]

5. filter() 方法

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

// 使用 filter() 方法过滤数组
const evenNumbers = numbers.filter(function(num) {
    return num % 2 === 0;
});

console.log(evenNumbers); // 输出: [2, 4, 6, 8, 10]
console.log(numbers); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10](原数组不变)

// 使用箭头函数
const oddNumbers = numbers.filter(num => num % 2 !== 0);
console.log(oddNumbers); // 输出: [1, 3, 5, 7, 9]

数组修改方法

1. push() 方法

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

// 使用 push() 方法向数组末尾添加元素
const newLength = fruits.push("葡萄", "梨");

console.log(fruits); // 输出: ["苹果", "香蕉", "橙子", "葡萄", "梨"]
console.log(newLength); // 输出: 5

2. pop() 方法

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

// 使用 pop() 方法删除数组末尾的元素
const removedElement = fruits.pop();

console.log(fruits); // 输出: ["苹果", "香蕉", "橙子"]
console.log(removedElement); // 输出: 葡萄

3. unshift() 方法

const fruits = ["香蕉", "橙子", "葡萄"];

// 使用 unshift() 方法向数组开头添加元素
const newLength = fruits.unshift("苹果", "梨");

console.log(fruits); // 输出: ["苹果", "梨", "香蕉", "橙子", "葡萄"]
console.log(newLength); // 输出: 5

4. shift() 方法

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

// 使用 shift() 方法删除数组开头的元素
const removedElement = fruits.shift();

console.log(fruits); // 输出: ["香蕉", "橙子", "葡萄"]
console.log(removedElement); // 输出: 苹果

5. splice() 方法

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

// 使用 splice() 方法删除元素
const removedElements = fruits.splice(1, 2);
console.log(fruits); // 输出: ["苹果", "葡萄"]
console.log(removedElements); // 输出: ["香蕉", "橙子"]

// 使用 splice() 方法添加元素
fruits.splice(1, 0, "香蕉", "橙子");
console.log(fruits); // 输出: ["苹果", "香蕉", "橙子", "葡萄"]

// 使用 splice() 方法替换元素
fruits.splice(2, 1, "梨");
console.log(fruits); // 输出: ["苹果", "香蕉", "梨", "葡萄"]

6. reverse() 方法

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

// 使用 reverse() 方法反转数组
fruits.reverse();

console.log(fruits); // 输出: ["葡萄", "橙子", "香蕉", "苹果"]

7. sort() 方法

// 排序字符串数组
const fruits = ["香蕉", "苹果", "葡萄", "橙子"];
fruits.sort();
console.log(fruits); // 输出: ["苹果", "橙子", "葡萄", "香蕉"]

// 排序数字数组(注意:默认按字符串排序)
const numbers = [10, 5, 8, 1, 3];
numbers.sort();
console.log(numbers); // 输出: [1, 10, 3, 5, 8](按字符串排序)

// 使用比较函数排序数字数组
numbers.sort(function(a, b) {
    return a - b;
});
console.log(numbers); // 输出: [1, 3, 5, 8, 10](按数字排序)

// 降序排序
numbers.sort(function(a, b) {
    return b - a;
});
console.log(numbers); // 输出: [10, 8, 5, 3, 1](降序排序)

// 使用箭头函数
numbers.sort((a, b) => a - b);
console.log(numbers); // 输出: [1, 3, 5, 8, 10]

数组访问方法

1. concat() 方法

const fruits1 = ["苹果", "香蕉"];
const fruits2 = ["橙子", "葡萄"];
const fruits3 = ["梨", "桃子"];

// 使用 concat() 方法连接数组
const allFruits = fruits1.concat(fruits2, fruits3);

console.log(allFruits); // 输出: ["苹果", "香蕉", "橙子", "葡萄", "梨", "桃子"]
console.log(fruits1); // 输出: ["苹果", "香蕉"](原数组不变)

2. slice() 方法

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

// 使用 slice() 方法截取数组
const subset1 = fruits.slice(1, 3); // 从索引 1 开始,到索引 3 结束(不包含 3)
const subset2 = fruits.slice(2); // 从索引 2 开始到末尾
const subset3 = fruits.slice(-3); // 从倒数第 3 个元素开始到末尾

console.log(subset1); // 输出: ["香蕉", "橙子"]
console.log(subset2); // 输出: ["橙子", "葡萄", "梨"]
console.log(subset3); // 输出: ["橙子", "葡萄", "梨"]
console.log(fruits); // 输出: ["苹果", "香蕉", "橙子", "葡萄", "梨"](原数组不变)

3. join() 方法

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

// 使用 join() 方法将数组转换为字符串
const result1 = fruits.join();
const result2 = fruits.join(", ");
const result3 = fruits.join("-");

console.log(result1); // 输出: 苹果,香蕉,橙子,葡萄
console.log(result2); // 输出: 苹果, 香蕉, 橙子, 葡萄
console.log(result3); // 输出: 苹果-香蕉-橙子-葡萄
console.log(fruits); // 输出: ["苹果", "香蕉", "橙子", "葡萄"](原数组不变)

4. indexOf() 方法

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

// 使用 indexOf() 方法查找元素的索引
const index1 = fruits.indexOf("香蕉");
const index2 = fruits.indexOf("梨");
const index3 = fruits.indexOf("香蕉", 2); // 从索引 2 开始查找

console.log(index1); // 输出: 1
console.log(index2); // 输出: -1(元素不存在)
console.log(index3); // 输出: 4

5. lastIndexOf() 方法

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

// 使用 lastIndexOf() 方法从末尾查找元素的索引
const index1 = fruits.lastIndexOf("香蕉");
const index2 = fruits.lastIndexOf("梨");
const index3 = fruits.lastIndexOf("香蕉", 3); // 从索引 3 开始向前查找

console.log(index1); // 输出: 4
console.log(index2); // 输出: -1(元素不存在)
console.log(index3); // 输出: 1

6. includes() 方法

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

// 使用 includes() 方法检查元素是否存在
const result1 = fruits.includes("香蕉");
const result2 = fruits.includes("梨");
const result3 = fruits.includes("橙子", 2); // 从索引 2 开始查找

console.log(result1); // 输出: true
console.log(result2); // 输出: false
console.log(result3); // 输出: true

数组归约方法

1. reduce() 方法

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

// 使用 reduce() 方法求和
const sum = numbers.reduce(function(accumulator, currentValue) {
    return accumulator + currentValue;
}, 0); // 0 是初始值

console.log(sum); // 输出: 15

// 使用 reduce() 方法求最大值
const max = numbers.reduce(function(accumulator, currentValue) {
    return Math.max(accumulator, currentValue);
}, -Infinity);

console.log(max); // 输出: 5

// 使用箭头函数
const product = numbers.reduce((acc, curr) => acc * curr, 1);
console.log(product); // 输出: 120

2. reduceRight() 方法

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

// 使用 reduceRight() 方法从右到左归约
const result = numbers.reduceRight(function(accumulator, currentValue) {
    return accumulator + currentValue;
}, 0);

console.log(result); // 输出: 15(结果与 reduce() 相同)

// 示例:连接字符串
const words = ["Hello", "World", "!"];
const sentence = words.reduceRight(function(acc, curr) {
    return curr + " " + acc;
}).trim();

console.log(sentence); // 输出: Hello World !

数组迭代方法

1. every() 方法

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

// 使用 every() 方法检查所有元素是否满足条件
const allPositive = numbers.every(function(num) {
    return num > 0;
});

const allEven = numbers.every(function(num) {
    return num % 2 === 0;
});

console.log(allPositive); // 输出: true
console.log(allEven); // 输出: false

2. some() 方法

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

// 使用 some() 方法检查是否有元素满足条件
const hasEven = numbers.some(function(num) {
    return num % 2 === 0;
});

const hasNegative = numbers.some(function(num) {
    return num < 0;
});

console.log(hasEven); // 输出: true
console.log(hasNegative); // 输出: false

3. find() 方法

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

// 使用 find() 方法查找第一个满足条件的元素
const firstEven = numbers.find(function(num) {
    return num % 2 === 0;
});

const firstGreaterThan5 = numbers.find(function(num) {
    return num > 5;
});

console.log(firstEven); // 输出: 2
console.log(firstGreaterThan5); // 输出: 6

4. findIndex() 方法

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

// 使用 findIndex() 方法查找第一个满足条件的元素的索引
const firstEvenIndex = numbers.findIndex(function(num) {
    return num % 2 === 0;
});

const firstGreaterThan5Index = numbers.findIndex(function(num) {
    return num > 5;
});

console.log(firstEvenIndex); // 输出: 1
console.log(firstGreaterThan5Index); // 输出: 5

数组转换方法

1. toString() 方法

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

// 使用 toString() 方法将数组转换为字符串
const result = fruits.toString();

console.log(result); // 输出: 苹果,香蕉,橙子,葡萄
console.log(typeof result); // 输出: string

2. toLocaleString() 方法

const numbers = [1000, 2000, 3000];
const dates = [new Date(2023, 0, 1), new Date(2023, 1, 1)];

// 使用 toLocaleString() 方法将数组转换为本地化字符串
const numberResult = numbers.toLocaleString();
const dateResult = dates.toLocaleString();

console.log(numberResult); // 输出: 1,000, 2,000, 3,000
console.log(dateResult); // 输出: 2023/1/1 00:00:00, 2023/2/1 00:00:00

数组最佳实践

  • 使用数组字面量创建数组:数组字面量语法简洁明了,是创建数组的首选方法
  • 使用 const 声明数组:除非需要重新赋值数组引用,否则使用 const 声明
  • 使用 for...of 或 forEach() 遍历数组:这些方法比传统的 for 循环更简洁
  • 使用 map() 转换数组:当需要基于原数组创建新数组时,使用 map() 方法
  • 使用 filter() 过滤数组:当需要基于条件筛选数组元素时,使用 filter() 方法
  • 使用 reduce() 归约数组:当需要将数组元素合并为单个值时,使用 reduce() 方法
  • 使用 includes() 检查元素是否存在:includes() 方法比 indexOf() 更直观
  • 注意数组方法的副作用:了解哪些方法会修改原数组,哪些方法会返回新数组
  • 使用扩展运算符复制数组:扩展运算符是复制数组的简洁方法
  • 避免使用 delete 删除数组元素:delete 会留下 empty 槽位,应使用 splice() 或 filter() 方法

数组常见问题

  • 问题 1: 数组索引从 0 开始
  • 解决方案:记住数组索引从 0 开始,第一个元素的索引是 0,第二个是 1,以此类推。

  • 问题 2: 数组长度是动态的
  • 解决方案:了解数组长度会自动调整,当添加或删除元素时,长度会相应变化。

  • 问题 3: sort() 方法默认按字符串排序
  • 解决方案:排序数字数组时,使用比较函数确保按数字顺序排序。

  • 问题 4: 稀疏数组
  • 解决方案:避免创建稀疏数组(含有 empty 槽位的数组),因为它们可能导致意外的行为。

  • 问题 5: 数组方法的兼容性
  • 解决方案:了解不同浏览器对数组方法的支持情况,对于旧浏览器可能需要使用 polyfill。

📝 学习检查

通过本章节的学习,你应该了解:

  • 数组的基本概念和创建方法
  • 数组元素的访问和修改
  • 数组遍历的不同方法
  • 数组修改和访问方法的使用
  • 数组归约和迭代方法的使用
  • 数组转换方法的使用
  • 数组的最佳实践和常见问题