# ⚪️ 【JavaScript 入門】 配列の使い方と操作まとめ(初期化・追加・結合・検索・削除)

# 🔶 【実践】「配列」の活用技

当然可以。下面是一个表格,展示了各个 JavaScript 数组方法的功能、返回值、是否改变原数组以及它们被引入的 ECMAScript 版本。

顺序 方法名 功能 返回值 是否改变原数组 版本
1 push() 向数组添加一个或多个元素(在数组尾部) 新数组的长度 ES5-
2 unshift() 向数组添加一个或多个元素(在数组头部) 新数组的长度 ES5-
3 pop() 删除数组的最后一个元素 被删除的元素 ES5-
4 shift() 删除数组的第一个元素 被删除的元素 ES5-
5 reverse() 反转数组中的元素 反转后的数组 ES5-
6 sort() 对数组进行排序(默认按字符串 Unicode 码点) 排序后的数组 ES5-
7 splice() 在指定位置删除或添加元素(可用于数组的增删改) 被删除的元素组成的数组 ES5-
8 concat() 合并多个数组,返回新数组 合并后的数组 ES5-
9 join() 将数组元素用特定字符连接成字符串 拼接后的字符串 ES5-
10 slice() 从数组中提取一段元素,返回新数组 提取的元素组成的新数组 ES5-
11 toString() 将数组转换为字符串 字符串表示的数组 ES5-
12 valueOf() 返回数组的原始值 数组的原始值 ES5-
13 indexOf() 查询数组中某元素首次出现的位置 元素位置,若不存在则返回-1 ES5-
14 lastIndexOf() 反向查询数组中某元素首次出现的位置 元素位置,若不存在则返回-1 ES5-
15 forEach() 遍历数组,对每个元素执行回调函数 无(undefined ES5-
16 map() 遍历数组,使用回调函数处理每个元素,并返回新数组 新数组 ES5-
17 filter() 遍历数组,筛选符合条件的元素,返回新数组 符合条件的元素组成的新数组 ES5-
18 every() 检测数组所有元素是否都满足条件 所有元素满足返回true,否则false ES5-
19 some() 检测数组中是否存在满足条件的元素 存在满足条件的元素返回true,否则false ES5-
20 reduce() 从左到右应用一个函数对数组元素进行累计/归约 操作结果 ES5-
21 reduceRight() 从右到左应用一个函数对数组元素进行累计/归约 操作结果 ES5-
22 includes()

判断数组是否包含特定值 | 包含则返回true,否则false | 否 | ES6 | | 23 | Array.from() | 从类数组或可迭代对象创建新数组 | 新数组 | 否 | ES6 | | 24 | find() | 查找数组中满足条件的第一个元素 | 满足条件的元素,否则undefined | 否 | ES6 | | 25 | findIndex() | 查找数组中满足条件元素的索引 | 满足条件元素的索引,否则-1 | 否 | ES6 | | 26 | fill() | 使用给定值填充数组 | 填充后的数组 | 是 | ES6 | | 27 | flat() | 将嵌套数组"拉平"成一维数组 | 新数组 | 否 | ES6 | | 28 | flatMap() | 先映射每个元素,然后将结果压平成一维数组 | 新数组 | 否 | ES6 |

# 🔶 方法详解

# 🔸 1. push()

  • 功能:在数组最后一位添加一个或多个元素,并返回新数组的长度。会改变原数组。
  • 示例:
    var arr = [1, 2, "c"];
    var rel = arr.push("A", "B");
    console.log(arr); // [1, 2, "c", "A", "B"]
    console.log(rel); // 5 (数组长度)
    

# 🔸 2. unshift()

  • 功能:在数组第一位添加一个或多个元素,并返回新数组的长度。会改变原数组。
  • 示例:
    var arr = [1, 2, "c"];
    var rel = arr.unshift("A", "B");
    console.log(arr); // ["A", "B", 1, 2, "c"]
    console.log(rel); // 5 (数组长度)
    

# 🔸 3. pop()

  • 功能:删除数组的最后一个元素,并返回被删除的元素。会改变原数组。
  • 示例:
    var arr = [1, 2, "c"];
    var rel = arr.pop();
    console.log(arr); // [1, 2]
    console.log(rel); // c
    

# 🔸 4. shift()

  • 功能:删除数组的第一个元素,并返回被删除的元素。会改变原数组。
  • 示例:
    var arr = ["a", "b", "c"];
    var rel = arr.shift();
    console.log(arr); // ["b", "c"]
    console.log(rel); // a
    

# 🔸 5. reverse()

  • 功能:反转数组中的元素。会改变原数组。
  • 示例:
    var arr = [1, 2, 3, "a", "b", "c"];
    var rel = arr.reverse();
    console.log(arr); // ["c", "b", "a", 3, 2, 1]
    console.log(rel); // ["c", "b", "a", 3, 2, 1]
    

# 🔸 6. sort()

  • 功能:对数组的元素进行排序。默认排序顺序是根据字符串 Unicode 码点。
  • 示例:
    var arr1 = [10, 1, 5, 2, 3];
    arr1.sort();
    console.log(arr1); // 默认排序结果
    // 使用自定义排序函数
    arr1.sort(function (a, b) {
      return a - b;
    });
    console.log(arr1); // 从小到大排序
    

# 🔸 7. splice()

# 功能:

  • 在指定位置添加或删除数组中的元素,或替换数组中的元素。会改变原数组。
  • splice 方法可以用于数组的增删改操作。
  • splice 方法的第一个参数是修改的起始位置(索引),第二个参数是删除的个数(如果是 0,则表示不删除元素),后面的参数是要添加进数组的元素。

# 语法:

  • array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
  • start:指定修改的开始位置(从 0 计数)。如果超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位(从-1 计数,这意味着-n 是倒数第 n 个元素并且等价于 array.length-n),如果负数的绝对值大于数组的长度,则表示开始位置为第 0 位。
  • deleteCount:整数,表示要移除的数组元素的个数。如果deleteCount大于start之后的元素的总数,则从start后面的元素都将被删除(含第start位)。
  • item1, item2, ...:要添加进数组的元素,从start位置开始。如果不指定,则只删除数组元素。
  • 返回值:由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。
  • 注意:splice 方法会改变原始数组。

# 示例:

var arr = ["a", "b", "c", 2, 3, 6];
var rel = arr.splice(2, 1, "add1", "add2");
console.log(arr); // 修改后的数组
console.log(rel); // 被删除的元素组成的数组

# 使用場景:

在 React 中,通常推荐使用不可变数据模式来更新状态,尤其是当处理数组和对象时。splice 方法会直接修改原数组,这可能导致 React 的状态更新行为表现得不如预期。

const remove = (index) => {
  const newGoods = [...goods];
  newGoods.splice(index, 1); // 删除 splice 方法, 会改变原数组, 返回被删除的元素
  setGoods(newGoods);
};

newGoods.splice(index, 1); 这行代码中,splice 被用于从 newGoods 数组中移除特定索引的元素。尽管这里使用了 newGoods 作为 goods 数组的副本,但由于数组是引用类型,在使用 splice 之前,应该创建 goods 的深拷贝。

# 为什么需要深拷贝?

当你使用 newGoods = [...goods] 这样的语句时,实际上创建的是原数组的浅拷贝。这意味着 newGoodsgoods 都指向相同的元素对象。当你在 newGoods 上使用 splice 时,虽然 goods 数组本身没有被直接修改,但数组中的对象可能会受到影响,这可能导致 React 的不预期行为。

# 正确的做法

为了避免这种问题,可以使用不会改变原数组的方法来处理数组。例如,可以使用 filter 方法来创建一个不包含特定索引元素的新数组,而不是使用 splice

const remove = (index) => {
  const newGoods = goods.filter((_, i) => i !== index);
  setGoods(newGoods);
};

在这个修改后的 remove 函数中,filter 方法被用来创建一个新的数组,其中不包含指定索引的元素。这种方法不会修改原数组,符合 React 的不可变数据原则,能够确保状态更新的可预测性。

# 🔸 8. concat()

  • 功能:连接两个或更多数组,并返回结果。不改变原数组。
  • 示例:
    var arr1 = [1, 2, 3];
    var arr2 = ["a", "b", "c"];
    var rel = arr1.concat(arr2);
    console.log(rel); // 合并后的数组
    

# 🔸 9. join()

  • 功能:将数组的所有元素连接成一个字符串。
  • 示例:
    var list = ["a", "b", "c", "d"];
    var result = list.join("-");
    console.log(result); // "a-b-c-d"
    

# 🔸 10. slice()

  • 功能:返回数组的一个片段或子数组。不改变原数组。
  • 示例:
    var list = ["a", "b", "c", "d"];
    var result = list.slice(1, 3);
    console.log(result); // ["b", "c"]
    

# 🔸 11. toString()

  • 功能:将数组转换为字符串。不改变原数组。
  • 示例:
    var list = ["a", "b", "c", "d"];
    var rel = list.toString();
    console.log(rel); // "a,b,c,d"
    

# 🔸 12. valueOf()

  • 功能:返回数组的原始值。
  • 示例:
    var list = [1, 2, 3, 4];
    var rel = list.valueOf();
    console.log(rel); // [1, 2, 3, 4]
    

# 🔸 13. indexOf()

  • 功能:返回指定元素在数组中首次出现的索引。不存在则返回-1。

  • 示例:

    var list = [1, 2, 3, 4];
    var index = list.indexOf(4);
    
    console.log(index); // 3
    

# 🔸 14. lastIndexOf()

  • 功能:返回指定元素在数组中最后一次出现的索引。不存在则返回-1。
  • 示例:
    var list = [1, 2, 3, 4];
    var index = list.lastIndexOf(4);
    console.log(index); // 3
    

# 🔸 15. forEach()

# 功能:

  • 对数组的每个元素执行一次提供的函数, 不改变原数组

    • forEach cannot directly handle asynchronous operations with await.(forEach `doesn't wait for promises to resolve before moving on to the next iteration.)
  • forEach()方法需要一个回调函数(这种函数,是由我们创建但是不由我们调用的)作为参数

    arr.forEach(function (item, index, array) {
      console.log(item, index, array);
    });
    
  • 回调函数中传递三个参数:

    • 第一个参数,就是当前正在遍历的元素
    • 第二个参数,就是当前正在遍历的元素的索引
    • 第三个参数,就是正在遍历的数组
  • forEach本身是没有中断机制的。内部是遍历执行回调的。

    • 在回调中增加判断条件,满足条件就抛出异常。(通常是不建议这么跳出循环的)
    • 使用 for(let item in arr) {}这样的遍历方式,可以提前跳出循环
    • 使用.some 方法,一些场景判断,满足条件可以提前结束

# 示例:

//基本for 循環支持 await, 但是效率會很低.
async function fetaData() {
  let arrs = Array.from({ length: 3 }, (_, index) => index + 1);
  let datas = [];
  for (let i = 0; i < arrs.length; i++) {
    let data = await fetch(
      `https://jsonplaceholder.typicode.com/todos/${arrs[i]}`
    )
      .then((res) => res.json())
      .then((res) => res);
    datas.push(data);
  }

  return datas;
}
fetaData().then((res) => console.log(res));

# 使用場景:

当您需要对数组中的每个元素执行异步操作,例如逐个上传图片,但不需要收集这些异步操作的返回值时,您可以使用 forEach 方法结合 async/await。请注意,虽然 forEach 不能直接处理 await,但您可以在 forEach 的回调函数中定义一个立即执行的异步函数来实现这一点。

以下是一个例子,演示了如何使用 forEach 方法逐个上传图片:

async function uploadImage(image) {
  // 这里是一个假设的上传函数,实际情况下应替换为实际的上传逻辑
  // 假设这个函数返回一个 Promise
  return fetch("https://example.com/upload", {
    method: "POST",
    body: image,
  }).then((response) => response.json());
}

function uploadImages(images) {
  images.forEach(async (image) => {
    try {
      const result = await uploadImage(image);
      console.log("Image uploaded:", result);
    } catch (error) {
      console.error("Error uploading image:", error);
    }
  });
}

// 假设这个数组包含了要上传的图片
const imageArray = ["image1.png", "image2.png", "image3.png"];

uploadImages(imageArray);

在这个例子中:

  1. uploadImage 函数是一个异步函数,用于上传单个图片,并返回一个 Promise 对象。
  2. uploadImages 函数接受一个图片数组,使用 forEach 遍历这个数组。
  3. forEach 的回调函数中,我们定义了一个立即执行的异步函数来处理图片上传。
  4. 每个图片上传的结果将会被打印出来,但整个 uploadImages 函数本身不会返回任何结果。

请注意,由于 forEach 并不等待异步操作完成,所有的上传操作将会几乎同时开始,这可能会对服务器造成压力。如果需要控制上传速度,比如一次只上传一个图片,您可能需要使用基本的 for 循环或其他方法来顺序执行异步操作。

# 其他可能場景:

  1. 处理菜单项

    • 如果您有一个菜单项数组,您可能需要使用 forEach 来遍历每个菜单项,以生成展示在网页上的菜单列表。
    • 对于每个菜单项,可能还需要根据特定属性(如类别、价格区间、是否为素食等)进行进一步处理或分类。
  2. 处理顾客评论

    • 如果您的网站有顾客评论功能,您可以使用 forEach 遍历评论数组,展示每个顾客的评分和评论。
  3. 订单处理

    • 在订单确认页面,您可能需要遍历订单中的每个项目,计算总价或应用折扣。
    • 如果需要将订单中的每个项目发送到后端处理(如库存检查、订单入库等),也可以使用 forEach
  4. 图片或媒体内容展示

    • 如果餐厅网站有图库展示餐厅内部、菜品等,您可以使用 forEach 来遍历图片数组,为每张图片创建相应的 HTML 元素。
  5. 员工管理

    • 在员工管理界面,forEach 可用于遍历员工列表,显示员工信息,或进行特定操作,如计算工资、安排班次等。
  6. 库存管理

    • 对于库存管理,forEach 可以用于遍历库存列表,更新库存状态或进行库存预警。
  7. 特殊活动或促销信息展示

    • 如果餐厅有特殊活动或促销,您可以使用 forEach 遍历活动数组,动态生成展示这些活动的界面元素。

在这些场景中,forEach 循环提供了一种简洁的方式来处理数组中的每个元素,尤其是当您不需要返回新数组时。这有助于编写更加清晰和可维护的代码。


# 🔸 16. map()

# 功能:

  • 數組原型是一個函數,對數組遍歷不破壞原數組, 返回一個新數組, 按照原是數組元素順序依次執行給定的函數, 並將每一次函數執行的結果作為新數組的元素返回

  • map 方法会遍历数组中的每个元素,并使用回调函数处理每个元素,最终返回一个新数组。

  • 參數: map 方法的回调函数接受三个参数:

    • element:就是当前正在遍历的元素
    • index, 就是当前正在遍历的元素的索引
    • array,就是正在遍历的数组, 調用了 map()的數組本身
  • 語法:

    • arr.map(callback(currentValue[, index[, array]])[, thisArg])
    • arr.map(callback(currentValue[, index[, array]])
      • thisArg 可选参数。执行 callback 函数时使用的 this 值。

# 示例:

  1. 将数组中的每个对象都添加一个新属性,并设置为相同的值。
let users = [{ name: "Alice" }, { name: "Bob" }, { name: "Charlie" }];
let updatedUsers = users.map((user) => ({
  ...user,
  isActive: true,
}));

console.log(updatedUsers); // [{ name: "Alice", isActive: true }, { name: "Bob", isActive: true }, { name: "Charlie", isActive: true }]
const numbers: number[] = [1, 2, 3, 4, 5];
const doubled: number[] = numbers.map((number: number) => number * 2);
  1. 從數組對象裡提取特定屬性
type User = {
  name: string;
  age: number;
};

const users: User[] = [
  { name: "Alice", age: 20 },
  { name: "Bob", age: 21 },
  { name: "Charlie", age: 22 },
];

const names: string[] = users.map((user: User) => user.name);
  1. 将字符串数组转换为对象数组
const fruits: string[] = ["apple", "banana", "cherry"];

type FruitObject = {
  name: string;
};

const fruitObjects: FruitObject[] = fruits.map((fruit: string) => ({
  name: fruit,
}));

console.log(fruitObjects); // [{ name: "apple" }, { name: "banana" }, { name: "cherry" }]
  1. 对象数组中的数字属性进行转换
type Product = {
  name: string;
  price: number;
  };

const products: Product[] = [
{ name: 'Book', price: 15 },
{ name: 'Pen', price: 5 },
{ name: 'Pencil', price: 2 }
];

const discountedProducts: Product[] = products.map((product: Product) => ({
...product,
price: product.price \* 0.9
}));

# 使用場景:

在 TypeScript 结合 React 开发的外卖应用(Web Restaurant App)中,使用.map方法来渲染列表是非常常见的。这里我将提供一些示例,展示如何在 TypeScript 环境下使用.map方法处理和渲染餐厅应用中的数据。

  1. 渲染菜单列表: 假设你有一个菜单项的数组,你想渲染这些菜单项到页面上。
interface MenuItem {
  id: number;
  name: string;
  price: number;
  description: string;
}

const menuItems: MenuItem[] = [
  { id: 1, name: "Burger", price: 5.99, description: "A classic burger" },
  { id: 2, name: "Pizza", price: 7.99, description: "Cheesy pizza" },
  // more menu items...
];

const MenuList: React.FC = () => (
  <div>
    {menuItems.map((item) => (
      <div key={item.id}>
        <h3>
          {item.name} - ${item.price}
        </h3>
        <p>{item.description}</p>
      </div>
    ))}
  </div>
);
  1. 订单详情: 在订单详情页,你可能需要列出用户所选的菜品及其价格。
interface OrderItem {
  id: number;
  name: string;
  quantity: number;
  price: number;
}

const orderItems: OrderItem[] = [
  // 假设这些数据是用户选择的菜品
];

const OrderDetails: React.FC = () => (
  <ul>
    {orderItems.map((item) => (
      <li key={item.id}>
        {item.name} x {item.quantity} - ${item.quantity * item.price}
      </li>
    ))}
  </ul>
);
  1. 评分和评论, 显示用户对菜品的评分和评论。
interface Review {
  id: number;
  user: string;
  rating: number;
  comment: string;
}

const reviews: Review[] = [
  // 用户评论数据
];

const ReviewsList: React.FC = () => (
  <div>
    {reviews.map((review) => (
      <div key={review.id}>
        <h4>{review.user}</h4>
        <p>Rating: {review.rating} / 5</p>
        <p>{review.comment}</p>
      </div>
    ))}
  </div>
);
  1. 如果你需要同时处理多个异步操作并等待它们全部完成,你可以使用 Promise.all 结合 map 方法,而不是 forEach。当您需要同时处理多个异步操作并等待它们全部完成时,可以使用 Promise.all 结合 map 方法。这种方法特别适用于需要并行执行多个异步请求并等待所有请求完成的情况。

示例:并行获取多个资源: 假设您有一个 URL 数组,您需要从每个 URL 获取数据。

const urls: string[] = [
  "https://api.example.com/data1",
  "https://api.example.com/data2",
  "https://api.example.com/data3",
  // 更多URLs...
];

// 定义一个异步函数来获取每个URL的数据
async function fetchData(url: string): Promise<any> {
  const response = await fetch(url);
  return response.json();
}

// 使用Promise.all和map来并行获取所有数据
async function getAllData() {
  try {
    const allData = await Promise.all(urls.map((url) => fetchData(url)));
    console.log(allData); // 打印所有获取到的数据
  } catch (error) {
    console.error("Error fetching data:", error);
  }
}

getAllData();

在这个示例中,urls.map(url => fetchData(url)) 会为每个 URL 创建一个 fetch 请求的 Promise。然后,Promise.all 接收这个 Promise 数组,并等待所有的 fetch 请求都完成。一旦所有请求完成,allData 变量将包含所有 URL 返回的数据。如果任何一个请求失败,catch 块将捕获错误。

注意事项:

  • 当使用 Promise.all 时,如果任何一个 Promise 失败,整个 Promise.all 调用会立即失败。这意味着如果您有多个请求,一个请求失败,其他成功的请求的结果也会被丢弃。如果您需要不同的行为(例如,处理每个请求的单独成功或失败),您可能需要考虑使用 Promise.allSettled 或单独处理每个 Promise 的错误。
  • 使用 Promise.all 可以显著提高性能,因为它允许异步操作并行执行,而不是按顺序一个接一个执行。 当然可以。在 TypeScript 和 React 结合使用的场景中,我们可以遇到更复杂的使用 map 方法的例子,特别是在处理嵌套数据结构或进行更高级的数据转换时。以下是一些复杂的使用场景示例:
  1. 渲染嵌套评论

假设你有一个嵌套评论的数据结构,你需要递归地渲染每个评论及其子评论。

interface Comment {
  id: number;
  text: string;
  user: string;
  replies: Comment[];
}

const comments: Comment[] = [
  // 假设的评论数据,每个评论可能有回复(也是Comment类型)
];

const renderComments = (comments: Comment[]): JSX.Element[] => {
  return comments.map((comment) => (
    <div key={comment.id}>
      <h4>{comment.user}</h4>
      <p>{comment.text}</p>
      {comment.replies && (
        <div className="replies">{renderComments(comment.replies)}</div>
      )}
    </div>
  ));
};

const CommentsList: React.FC = () => <div>{renderComments(comments)}</div>;
  1. 动态生成表格列

在一个数据驱动的应用中,你可能需要根据数据对象的属性动态生成表格列。

interface Product {
  id: number;
  name: string;
  price: number;
  stock: number;
}

const products: Product[] = [
  // 产品数据
];

const ProductTable: React.FC = () => (
  <table>
    <thead>
      <tr>
        {Object.keys(products[0]).map((key) => (
          <th key={key}>{key.toUpperCase()}</th>
        ))}
      </tr>
    </thead>
    <tbody>
      {products.map((product) => (
        <tr key={product.id}>
          {Object.values(product).map((value, index) => (
            <td key={index}>{value}</td>
          ))}
        </tr>
      ))}
    </tbody>
  </table>
);
  1. 使用 map 处理 TypeScript 枚举

当你有一个 TypeScript 枚举,并希望基于枚举的值生成一组元素时。

enum OrderStatus {
  Pending = "pending",
  InProgress = "in_progress",
  Completed = "completed",
  Cancelled = "cancelled",
}

const StatusSelector: React.FC = () => (
  <select>
    {Object.values(OrderStatus).map((status) => (
      <option key={status} value={status}>
        {status}
      </option>
    ))}
  </select>
);
  1. 使用 Promise.all 与类型保护

在处理多个异步请求时,你可能还需要进行类型保护,以确保每个响应都符合预期的类型。

type ApiResponse = DataResponse | ErrorResponse;

interface DataResponse {
  status: "ok";
  data: any;
}

interface ErrorResponse {
  status: "error";
  message: string;
}

const fetchData = async (url: string): Promise<ApiResponse> => {
  try {
    const response = await fetch(url);
    const data = await response.json();
    return { status: "ok", data };
  } catch (error) {
    return { status: "error", message: error.message };
  }
};

async function getAllData(urls: string[]) {
  const responses = await Promise.all(urls.map(fetchData));
  responses.forEach((response) => {
    if (response.status === "ok") {
      console.log("Data:", response.data);
    } else {
      console.error("Error:", response.message);
    }
  });
}

在这个示例中,ApiResponse 类型是一个联合类型,包括 DataResponseErrorResponse。当处理 Promise.all 的结果时,使用类型保护来确定每个响应是成功的数据响应还是错误响应。

这些例子展示了在更复杂的场景中使用 .map 方法的多样性,尤其是在 TypeScript 环境中处理类型安全和异步操作时。


# 🔸 17. filter()

# 功能:

  • filter 方法会遍历数组中的每个元素,并使用回调函数处理每个元素,最终返回一个新数组。

  • 在回调函数中,返回值为 true 的元素将会被保留,返回值为 false 的元素将会被过滤掉。

  • filter 方法不会改变原数组。

  • filter 方法的回调函数接受三个参数:

    • element:就是当前正在遍历的元素
    • index, 就是当前正在遍历的元素的索引
    • array,就是正在遍历的数组, 調用了 filter()的數組本身
    • thisArg 可选参数。执行 callback 函数时使用的 this 值。
  • 語法:

    • arr.filter(callback(currentValue[, index[, array]])[, thisArg])
    • arr.filter(callback(currentValue[, index[, array]])

# 示例:

当然可以。下面是一些使用 filter 方法的 TypeScript 示例,包括处理数组对象,并在某些情况下利用索引。

  1. 过滤特定条件的对象
interface Product {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
}

const products: Product[] = [
  { id: 1, name: "Apple", price: 1.2, inStock: true },
  { id: 2, name: "Banana", price: 0.5, inStock: false },
  { id: 3, name: "Cherry", price: 2.5, inStock: true },
];

// 过滤出库存中的产品
const inStockProducts: Product[] = products.filter(
  (product) => product.inStock
);
  1. 使用索引过滤
const data: number[] = [10, 20, 30, 40, 50];

// 只保留偶数索引的元素
const filteredData: number[] = data.filter((_, index) => index % 2 === 0);
  1. 结合对象和索引过滤
interface User {
  id: number;
  name: string;
  age: number;
}

const users: User[] = [
  { id: 1, name: "Alice", age: 24 },
  { id: 2, name: "Bob", age: 30 },
  { id: 3, name: "Carol", age: 22 },
];

// 过滤出年龄大于25的用户,并且只保留偶数索引的用户
const selectedUsers: User[] = users.filter(
  (user, index) => user.age > 25 && index % 2 === 0
);
  1. 移除数组中的重复元素
const numbers: number[] = [1, 2, 3, 2, 3, 4, 5, 4];

const uniqueNumbers: number[] = numbers.filter(
  (value, index, arr) => arr.indexOf(value) === index
);
  1. 根据多个条件过滤
interface Book {
  id: number;
  title: string;
  author: string;
  year: number;
}

const books: Book[] = [
  { id: 1, title: "1984", author: "George Orwell", year: 1949 },
  {
    id: 2,
    title: "The Great Gatsby",
    author: "F. Scott Fitzgerald",
    year: 1925,
  },
  { id: 3, title: "Brave New World", author: "Aldous Huxley", year: 1932 },
];

// 过滤出在1930年之后出版的书籍,并且作者为 "Aldous Huxley"
const filteredBooks: Book[] = books.filter(
  (book) => book.year > 1930 && book.author === "Aldous Huxley"
);
  1. mapfilter 鏈式調用,數組對象裡提取特定屬性
type User = {
  id: number;
  name: string;
  age: number;
};

const users: User[] = [
  { id: 1, name: "Alice" },
  { id: null, name: "Bob" },
  { id: 2, name: "Bob" },
  { id: 3, name: "Charlie" },
];

const newUsers = users
  .filter((item) => item.id)
  .map((item) => ({ ...item, isMember: true }));

console.log(newUsers); // [{ id: 1, name: "Alice", isMember: true }, { id: 2, name: "Bob", isMember: true }, { id: 3, name: "Charlie", isMember: true }]

# 其他使用場景:

在一个复杂的餐厅程序中,我们可以设想几个更复杂的 filter 使用场景,这些场景涉及多条件筛选、嵌套数据结构以及和其他数组方法的结合使用。以下是一些 TypeScript 示例:

  1. 根据多个条件筛选菜单项

假设你的餐厅应用需要根据多个条件(如价格范围、食物类型、客户评分)筛选菜单项。

interface MenuItem {
  id: number;
  name: string;
  type: "starter" | "main" | "dessert";
  price: number;
  averageRating: number;
}

const menuItems: MenuItem[] = [
  // 菜单项数据...
];

const filterCriteria = {
  type: "main",
  priceRange: { min: 10, max: 20 },
  minRating: 4,
};

const filteredMenuItems = menuItems.filter(
  (item) =>
    item.type === filterCriteria.type &&
    item.price >= filterCriteria.priceRange.min &&
    item.price <= filterCriteria.priceRange.max &&
    item.averageRating >= filterCriteria.minRating
);
  1. 筛选包含特定配料的菜品及其变体

考虑一个复杂的场景,其中菜品可能有多个变体(如不同的调味方式或配料)。你需要找到包含或排除特定配料的所有菜品及其变体。

interface MenuItemVariant {
  variantId: number;
  ingredients: string[];
}

interface MenuItem {
  id: number;
  name: string;
  variants: MenuItemVariant[];
}

const menuItems: MenuItem[] = [
  // 菜单项及变体数据...
];

const ingredientFilter = "cheese";

const itemsWithIngredient = menuItems.filter((item) =>
  item.variants.some((variant) =>
    variant.ingredients.includes(ingredientFilter)
  )
);
  1. 结合 filtermap 筛选并转换数据

在某些情况下,你可能需要先筛选出符合条件的数据,然后转换这些数据以用于显示。

const menuItemsWithRatings: MenuItem[] = [
  // 含评分的菜单项数据...
];

const highRatedDishes = menuItemsWithRatings
  .filter((item) => item.averageRating >= 4)
  .map((item) => ({
    name: item.name,
    rating: item.averageRating,
    priceRange: item.price >= 20 ? "High" : "Medium",
  }));
  1. 复杂订单过滤

考虑到餐厅可能需要处理大量的订单数据,你可能需要根据订单的多个属性来筛选它们,如订单状态、金额、下单时间等。

interface Order {
  id: number;
  totalAmount: number;
  status: "new" | "processing" | "delivered" | "cancelled";
  orderDate: Date;
}

const orders: Order[] = [
  // 订单数据...
];

const filteredOrders = orders.filter(
  (order) =>
    order.status === "delivered" &&
    order.totalAmount > 50 &&
    order.orderDate > new Date("2021-01-01")
);

# 🔸 18. every()

# 功能:

  • 检测数组所有元素是否都满足指定条件。
  • every 方法会遍历数组中的每个元素,并使用回调函数处理每个元素,最终返回一个布尔值。
  • 在回调函数中,如果所有元素都满足条件every 方法将返回 true,否则返回 false

# 示例:

var list = [32, 93, 77, 53, 38, 87];
var result = list.every(function (item) {
  return item >= 50;
});
console.log(result); // false

# 使用場景

当您需要检测数组中的所有元素是否满足指定条件时,可以使用 every 方法。以下是一些 TypeScript 示例:

  1. 检测订单中的所有商品是否都有库存
interface OrderItem {
  productId: number;
  quantity: number;
  price: number;
}

const orderItems: OrderItem[] = [
  { productId: 1, quantity: 2, price: 10 },
  { productId: 2, quantity: 1, price: 20 },
  // 更多商品...
];

const allItemsInStock = orderItems.every((item) => item.quantity > 0);

# 🔸 19. some()

# 功能:

  • 检测数组中是否有元素满足指定条件。
  • some 方法会遍历数组中的每个元素,并使用回调函数处理每个元素,最终返回一个布尔值。
  • 在回调函数中,如果有一个元素满足条件some 方法将返回 true,否则返回 false

# 示例:

var list = [32, 93, 77, 53, 38, 87];
var result = list.some(function (item) {
  return item >= 50;
});
console.log(result); // true

every 方法类似,some 方法也可以用于检测数组中是否有元素满足指定条件。但是,some 方法只要有一个元素满足条件,就会返回 true,而不是所有元素都满足条件。

# 🔸 20. reduce()

# 功能:

  • 对数组中的每个元素执行一个由您提供的 reducer 函数(升序执行),将其结果汇总为单个返回值。

  • reduce 方法對數組中每一個元素按序執行一個指定方法, 每一次允許reducer 會將先前元素的計算結果作為參數傳入, 最後返回一個累積的結果

  • reduce 方法接受两个参数:

    • reducer 函数:用于处理数组中的每个元素,并将其结果汇总为单个返回值。
    • initialValue:作为第一次调用 reducer 函数时的第一个参数使用的值。第一次回調函數的初始值,如果指定初始值,則會作為第一次調用 callback 函數時的第一個參數使用。如果沒有提供初始值,則將使用數組中的第一個元素。在沒有初始值的空數組上調用 reduce 將報錯。
  • reducer 函数接受四个参数:

    • accumulator:累加器累计回调的返回值; 它是上一次调用回调时返回的累积值,或 initialValue
    • currentValue:数组中正在处理的元素。
    • currentIndex:数组中正在处理的元素的索引。如果提供了 initialValue,则索引号为 0,否则索引为 1。
    • array:调用 reduce 的数组。
    • thisArg 可选参数。执行 callback 函数时使用的 this 值。

# 示例:

  1. 计算订单总金额: 假设你有一个表示订单中各个商品及其数量的数组,你需要计算订单的总金额。
interface OrderItem {
  productId: number;
  quantity: number;
  price: number;
}

const orderItems: OrderItem[] = [
  { productId: 1, quantity: 2, price: 10 },
  { productId: 2, quantity: 1, price: 20 },
  // 更多商品...
];

const totalAmount = orderItems.reduce((total, item) => {
  return total + item.quantity * item.price;
}, 0);
  1. 将数组转换为对象 使用reduce 方法可以将数组转换为更复杂的数据结构,比如对象。
interface User {
  id: number;
  name: string;
}

const users: User[] = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  // 更多用户...
];
//累加器:reduce 方法的第一个参数是一个回调函数,这个回调函数接收两个参数:当前的累加器(obj)和当前正在处理的数组元素(user)。
const usersById = users.reduce((obj, user) => {
  obj[user.id] = user; // *将用户对象添加到以用户ID为键的对象中*
  return obj;

  console.log(obj); // { 1: { id: 1, name: "Alice" }, 2: { id: 2, name: "Bob" } }
}, {} as { [key: number]: User }); // 以空对象作为初始值

console.log(usersById); // { 1: { id: 1, name: "Alice" }, 2: { id: 2, name: "Bob" } }

当你看到这种写法 obj[user.id] = user;,它是一种在 JavaScript 和 TypeScript 中常用的方式,用于将数组中的元素映射到一个对象的属性上。具体来说,这种写法是在构造一个以 user.id 作为键(key),user 对象本身作为值(value)的对象。

在这个表达式中:

  • obj 是一个对象,通常是一个空对象 {},它在 reduce 函数的迭代过程中不断被更新。
  • user 是当前正在迭代的数组元素。
  • user.iduser 对象的一个属性,这里用作新对象的键。
  • obj[user.id] 表示在 obj 对象中创建或更新一个以 user.id 的值为键的属性。
  • obj[user.id] = user; 表示将当前的 user 对象赋值给这个键。
  1. 分类汇总数据: 对数组中的项目进行分类,并计算每个类别中的项目数量。
interface Product {
  id: number;
  category: string;
}

const products: Product[] = [
  { id: 1, category: "Electronics" },
  { id: 2, category: "Books" },
  // 更多产品...
];

const categoryCount = products.reduce((count, product) => {
  count[product.category] = (count[product.category] || 0) + 1;
  return count;
}, {} as { [key: string]: number });

{} as { [key: string]: number }); 是一種 type assertion, 用來告訴編譯器, 這個空對象的類型是 { [key: string]: number }{ [key: string]: number }:这是一个索引签名类型。它描述了一个对象,这个对象可以拥有任意数量的属性,但所有属性的键(key)都是字符串类型,而对应的值(value)都是数字类型。

[key: string]: 表示对象的键是字符串类型。key 在这里只是一个占位符,你可以使用任何名称。 number: 表示属性值的类型必须是数字。

  1. 创建值的累积数组: 创建一个新数组,其中每个元素是原始数组中对应元素及其之前所有元素的累积和。
const numbers = [1, 2, 3, 4, 5];

const cumulativeSum = numbers.reduce((acc, value) => {
  if (acc.length > 0) {
    acc.push(value + acc[acc.length - 1]);
  } else {
    acc.push(value);
  }
  return acc;
}, [] as number[]);

# 使用場景:

在餐厅应用程序(Restaurant App)的开发中,reduce 方法可以在许多场景中发挥重要作用,尤其是在处理数据汇总、统计分析以及复杂的数组转换时。以下是一些针对餐厅应用的 TypeScript 示例,展示了 reduce 方法的不同用途:

  1. 统计不同类型菜品的数量

假设你的餐厅应用需要对菜单中不同类型的菜品进行统计。

interface MenuItem {
  id: number;
  name: string;
  type: "starter" | "main" | "dessert";
}

const menuItems: MenuItem[] = [
  // 菜单数据...
];

const itemCountByType = menuItems.reduce((count, item) => {
  count[item.type] = (count[item.type] || 0) + 1;
  return count;
}, {} as { [key: string]: number });
  1. 计算每日总销售额

计算一天内所有订单的总销售额。

interface Order {
  id: number;
  totalAmount: number;
  date: Date;
}

const orders: Order[] = [
  // 当日订单数据...
];

const totalSales = orders.reduce(
  (total, order) => total + order.totalAmount,
  0
);
  1. 汇总顾客反馈

将所有顾客的评论汇总成一个字符串。

interface Review {
  id: number;
  comment: string;
}

const reviews: Review[] = [
  // 顾客评论数据...
];

const allComments = reviews.reduce(
  (comments, review) => comments + review.comment + " ",
  ""
);
  1. 创建菜品成分列表

假设你需要从所有菜品中创建一个包含所有独特成分的列表。

interface Dish {
  id: number;
  ingredients: string[];
}

const dishes: Dish[] = [
  // 菜品数据...
];

const allIngredients = dishes
  .reduce(
    (ingredients, dish) => ingredients.concat(dish.ingredients),
    [] as string[]
  )
  .filter((value, index, self) => self.indexOf(value) === index); // 移除重复项
  1. 分类订单并计算每类的总金额

假设你需要按顾客类型(比如会员和非会员)分类订单,并计算每一类的总金额。

interface CustomerOrder {
  id: number;
  customerId: number;
  totalAmount: number;
  isMember: boolean;
}

const customerOrders: CustomerOrder[] = [
  // 顾客订单数据...
];

const totalAmountByCustomerType = customerOrders.reduce((totals, order) => {
  const key = order.isMember ? "members" : "nonMembers";
  totals[key] = (totals[key] || 0) + order.totalAmount;
  return totals;
}, {} as { members: number; nonMembers: number });

# 🔸 21. reduceRight()

  • 功能:类似于reduce(),但从右到左执行。
  • 示例:
    // 示例与`reduce()`类似,仅改变迭代方向
    

# 🔸 22. includes()

# 功能:

  • 判断数组是否包含指定的值, 返回布尔值。

  • 只能檢測基本類型的值, 不能檢測引用類型的值

  • includes 方法接受两个参数:

    • searchElement:要查找的元素。
    • fromIndex:可选参数。开始查找的位置。如果省略,则从数组的第一个元素(索引位置 0)开始查找。如果该值为负数,则按升序从 array.length + fromIndex 的索引开始搜索。如果 fromIndex 大于或等于数组的长度,则 includes 不会查找数组,返回 false

# 示例:

let site = ["facebook", "google", "youtube"];
console.log(site.includes("youtube")); // true
console.log(site.includes("yahoo")); // false

# 23.🔸 from()

  • 功能:从类数组或可迭代对象创建一个新数组。
  • 示例:
    var all = {
      0: "张飞",
      1: "28",
      2: "男",
      3: ["率土", "鸿图", "三战"],
      length: 4,
    };
    var list = Array.from(all);
    console.log(list);
    

# 24. 🔸 find()

  • 功能:返回数组中满足提供的测试函数的第一个元素的值。否则返回undefined
  • 示例:
    var list = [55, 66, 77, 88, 99, 100];
    var res = list.find(function (item) {
      return item > 60;
    });
    console.log(res); // 66
    

# 25. 🔸 findIndex()

  • 功能:返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。
  • 示例:
    var list = [55, 66, 77, 88, 99, 100];
    var index = list.findIndex(function (item) {
      return item > 60;
    });
    console.log(index); // 1
    

# 26. 🔸 fill()

  • 功能:用一个固定值填充数组中从起始索引到终止索引内的全部元素。
  • 示例:
    var result = ["a", "b", "c"].fill("填充", 1, 2);
    console.log(result); // ["a", "填充", "c"]
    

# 27. 🔸 flat()

  • 功能:创建一个新数组,其中所有子数组元素递归地连接到指定深度。

  • 示例:

    var list = [1, 2, [3, 4, [5]]];
    var arr = list.flat();
    console.log("拉平一次", arr); // [1, 2, 3, 4, [5]]
    
    var arr2 = list.flat(2); // 拉平两次
    console.log("拉平两次", arr2); // [1, 2, 3, 4, 5]
    

# 28. 🔸 flatMap()

  • 功能:首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。
  • 示例:
    var list = [55, 66, 77, 88, 99, 100];
    var newArr = list.flatMap(function (item, index) {
      return [item, index];
    });
    console.log("flatMap方法:", newArr);
    // 结果: [[55, 0], [66, 1], [77, 2], [88, 3], [99, 4], [100, 5]]