Skip to content

数组搜索方法:精准定位你需要的数据

在一个拥有数百万本书的图书馆中,你是如何找到一本特定的书籍?可能通过书名查找、按作者搜索、或者根据 ISBN 编号精确定位。数组搜索方法就像是数据世界中的图书馆检索系统,它们帮助我们在大量数据中快速找到所需的信息。无论是查找某个特定值、定位符合条件的元素,还是验证某项数据是否存在,JavaScript 提供了一套完整而强大的搜索工具。

indexOf() - 从前往后查找索引

indexOf() 方法返回数组中第一个匹配元素的索引。它从数组的开头开始搜索,找到第一个匹配项就停止。

基本用法

javascript
let fruits = ["apple", "banana", "orange", "banana", "grape"];

// 查找 "banana" 的位置
let position = fruits.indexOf("banana");
console.log(position); // 1

// 查找不存在的元素返回 -1
let notFound = fruits.indexOf("mango");
console.log(notFound); // -1

indexOf() 找到匹配的元素时,它返回该元素的索引位置。如果没有找到,返回 -1。这个返回值非常重要,因为它既告诉你元素是否存在,又告诉你它在哪里。

指定开始搜索的位置

indexOf() 接受第二个可选参数,用于指定搜索的起始位置:

javascript
let numbers = [10, 20, 30, 20, 40, 20, 50];

// 从索引 0 开始查找
console.log(numbers.indexOf(20)); // 1

// 从索引 2 开始查找
console.log(numbers.indexOf(20, 2)); // 3

// 从索引 4 开始查找
console.log(numbers.indexOf(20, 4)); // 5

// 如果起始位置是负数,从末尾开始计算
console.log(numbers.indexOf(20, -3)); // 5
// -3 相当于 7 - 3 = 索引 4,从索引 4 开始查找

第二个参数让你可以跳过已经检查过的部分,这在需要查找所有匹配项时特别有用。

实际应用:查找所有匹配项

javascript
function findAllIndices(array, value) {
  let indices = [];
  let index = array.indexOf(value);

  while (index !== -1) {
    indices.push(index);
    // 从下一个位置继续查找
    index = array.indexOf(value, index + 1);
  }

  return indices;
}

let colors = ["red", "blue", "red", "green", "red", "yellow"];
let redPositions = findAllIndices(colors, "red");
console.log(redPositions); // [0, 2, 4]

这个函数通过反复调用 indexOf() 并更新起始位置,找出数组中所有匹配值的索引。

严格相等比较

indexOf() 使用严格相等(===)进行比较,这意味着类型和值都必须完全匹配:

javascript
let mixed = [1, "1", 2, "2", 3];

console.log(mixed.indexOf(1)); // 0
console.log(mixed.indexOf("1")); // 1 - 不同的索引,因为类型不同

// 对于对象,比较的是引用
let obj1 = { name: "Alice" };
let obj2 = { name: "Alice" };
let objects = [obj1, { name: "Bob" }];

console.log(objects.indexOf(obj1)); // 0 - 找到了
console.log(objects.indexOf(obj2)); // -1 - 没找到,因为是不同的对象引用
console.log(objects.indexOf({ name: "Alice" })); // -1 - 新对象,引用不同

lastIndexOf() - 从后往前查找索引

lastIndexOf()indexOf() 功能相似,但搜索方向相反——它从数组末尾开始向前搜索。

javascript
let letters = ["a", "b", "c", "b", "d", "b"];

// 从后往前找第一个 "b"
console.log(letters.lastIndexOf("b")); // 5

// indexOf 从前往后找第一个
console.log(letters.indexOf("b")); // 1

// 指定搜索的结束位置(从这个位置向前搜索)
console.log(letters.lastIndexOf("b", 4)); // 3
console.log(letters.lastIndexOf("b", 2)); // 1

lastIndexOf() 在需要从后向前查找时非常有用,比如查找文件扩展名或路径中的最后一个分隔符。

实际应用:提取文件扩展名

javascript
function getFileExtension(filename) {
  let dotIndex = filename.lastIndexOf(".");

  if (dotIndex === -1) {
    return ""; // 没有扩展名
  }

  return filename.slice(dotIndex + 1);
}

console.log(getFileExtension("document.pdf")); // "pdf"
console.log(getFileExtension("archive.tar.gz")); // "gz"
console.log(getFileExtension("README")); // ""
console.log(getFileExtension("my.file.name.txt")); // "txt"

通过从后向前查找点号,我们可以正确处理文件名中包含多个点号的情况,只提取真正的扩展名。

includes() - 检查元素是否存在

includes() 方法用于判断数组是否包含某个值,返回布尔值。相比 indexOf(),它的语义更清晰,专注于"存在性"而非"位置"。

javascript
let inventory = ["laptop", "mouse", "keyboard", "monitor"];

// 检查是否有某个产品
console.log(inventory.includes("mouse")); // true
console.log(inventory.includes("tablet")); // false

// 使用条件判断
if (inventory.includes("keyboard")) {
  console.log("Keyboard is in stock");
}

// 对比 indexOf() 的写法
if (inventory.indexOf("keyboard") !== -1) {
  console.log("Keyboard is in stock");
}

includes() 的代码更简洁、更易读。当你只关心元素是否存在,而不关心它的位置时,应该优先使用 includes()

指定搜索起始位置

indexOf() 一样,includes() 也可以指定搜索的起始位置:

javascript
let numbers = [1, 2, 3, 4, 5, 3, 6];

console.log(numbers.includes(3)); // true
console.log(numbers.includes(3, 3)); // true - 从索引 3 开始,找到了索引 5 的 3
console.log(numbers.includes(3, 6)); // false - 从索引 6 开始,后面没有 3 了

NaN 的特殊处理

includes() 相比 indexOf() 的一个重要优势是它能正确处理 NaN

javascript
let values = [1, 2, NaN, 4];

// includes() 可以找到 NaN
console.log(values.includes(NaN)); // true

// indexOf() 找不到 NaN
console.log(values.indexOf(NaN)); // -1

这是因为 includes() 内部使用了更智能的比较算法(类似于 Object.is()),而 indexOf() 使用严格相等比较,而 NaN === NaNfalse

实际应用:权限检查

javascript
function hasPermission(user, requiredPermission) {
  return user.permissions.includes(requiredPermission);
}

let adminUser = {
  name: "Sarah",
  permissions: ["read", "write", "delete", "admin"],
};

let regularUser = {
  name: "Michael",
  permissions: ["read", "write"],
};

console.log(hasPermission(adminUser, "delete")); // true
console.log(hasPermission(regularUser, "delete")); // false
console.log(hasPermission(regularUser, "read")); // true

find() - 查找满足条件的第一个元素

find() 方法返回数组中第一个满足测试函数的元素值。它不仅能查找特定值,还能根据复杂条件进行搜索。

javascript
let users = [
  { id: 1, name: "Alice", age: 25 },
  { id: 2, name: "Bob", age: 30 },
  { id: 3, name: "Charlie", age: 35 },
  { id: 4, name: "David", age: 30 },
];

// 查找年龄为 30 的第一个用户
let user = users.find((u) => u.age === 30);
console.log(user); // { id: 2, name: "Bob", age: 30 }

// 查找名字以 "C" 开头的用户
let userC = users.find((u) => u.name.startsWith("C"));
console.log(userC); // { id: 3, name: "Charlie", age: 35 }

// 如果没找到,返回 undefined
let notFound = users.find((u) => u.age > 40);
console.log(notFound); // undefined

find() 接受一个回调函数作为参数,这个函数会对数组中的每个元素执行,直到找到第一个使回调函数返回 true 的元素。回调函数接收三个参数:当前元素、当前索引和原数组。

回调函数的参数

javascript
let products = [
  { id: 101, name: "Laptop", price: 999, inStock: true },
  { id: 102, name: "Mouse", price: 25, inStock: false },
  { id: 103, name: "Keyboard", price: 75, inStock: true },
];

let product = products.find((item, index, array) => {
  console.log(`Checking index ${index}:`, item.name);
  return item.price < 100 && item.inStock;
});

// 输出:
// Checking index 0: Laptop
// Checking index 1: Mouse
// Checking index 2: Keyboard

console.log(product); // { id: 103, name: "Keyboard", price: 75, inStock: true }

一旦找到匹配的元素,find() 就会停止遍历并返回该元素,这提高了性能。

实际应用:用户登录验证

javascript
function authenticateUser(email, password, userDatabase) {
  let user = userDatabase.find((u) => u.email === email);

  if (!user) {
    return { success: false, message: "User not found" };
  }

  if (user.password !== password) {
    return { success: false, message: "Invalid password" };
  }

  return { success: true, user: user };
}

let users = [
  { email: "[email protected]", password: "pass123", name: "Alice" },
  { email: "[email protected]", password: "secret456", name: "Bob" },
];

console.log(authenticateUser("[email protected]", "pass123", users));
// { success: true, user: { email: "[email protected]", ... } }

console.log(authenticateUser("[email protected]", "wrong", users));
// { success: false, message: "Invalid password" }

findIndex() - 查找满足条件的第一个元素索引

findIndex()find() 类似,但返回的是元素的索引而不是元素本身。

javascript
let students = [
  { name: "Emma", grade: 85 },
  { name: "James", grade: 92 },
  { name: "Olivia", grade: 78 },
  { name: "William", grade: 95 },
];

// 查找第一个成绩低于 80 的学生索引
let index = students.findIndex((student) => student.grade < 80);
console.log(index); // 2

// 使用索引访问元素
if (index !== -1) {
  console.log(`Student needing help: ${students[index].name}`);
  // Student needing help: Olivia
}

// 找不到返回 -1
let highAchiever = students.findIndex((student) => student.grade > 100);
console.log(highAchiever); // -1

findIndex() 在需要知道元素位置以便进行后续操作(如修改或删除)时特别有用。

实际应用:更新购物车

javascript
function updateCartQuantity(cart, productId, newQuantity) {
  let index = cart.findIndex((item) => item.productId === productId);

  if (index !== -1) {
    cart[index].quantity = newQuantity;
    return true;
  }

  return false; // 商品不在购物车中
}

let shoppingCart = [
  { productId: 1, name: "Laptop", quantity: 1 },
  { productId: 2, name: "Mouse", quantity: 2 },
  { productId: 3, name: "Keyboard", quantity: 1 },
];

updateCartQuantity(shoppingCart, 2, 5);
console.log(shoppingCart);
// [
//   { productId: 1, name: "Laptop", quantity: 1 },
//   { productId: 2, name: "Mouse", quantity: 5 },  ✓ 更新了
//   { productId: 3, name: "Keyboard", quantity: 1 }
// ]

findLast() - 从后往前查找满足条件的元素

findLast() 是 ES2023 引入的新方法,从数组末尾向前搜索,返回最后一个满足条件的元素。

javascript
let transactions = [
  { id: 1, type: "deposit", amount: 100, date: "2024-01-01" },
  { id: 2, type: "withdrawal", amount: 50, date: "2024-01-05" },
  { id: 3, type: "deposit", amount: 200, date: "2024-01-10" },
  { id: 4, type: "withdrawal", amount: 75, date: "2024-01-15" },
  { id: 5, type: "deposit", amount: 150, date: "2024-01-20" },
];

// 查找最后一次存款记录
let lastDeposit = transactions.findLast((t) => t.type === "deposit");
console.log(lastDeposit);
// { id: 5, type: "deposit", amount: 150, date: "2024-01-20" }

// 对比 find() - 返回第一次存款
let firstDeposit = transactions.find((t) => t.type === "deposit");
console.log(firstDeposit);
// { id: 1, type: "deposit", amount: 100, date: "2024-01-01" }

findLast() 在处理时间序列数据或需要获取最新记录时非常有用。

实际应用:获取用户最新状态

javascript
let userStatusHistory = [
  { timestamp: "2024-01-01 09:00", status: "online" },
  { timestamp: "2024-01-01 12:00", status: "away" },
  { timestamp: "2024-01-01 14:00", status: "online" },
  { timestamp: "2024-01-01 17:00", status: "offline" },
  { timestamp: "2024-01-02 09:00", status: "online" },
];

// 获取最后一次在线状态
let lastOnline = userStatusHistory.findLast((s) => s.status === "online");
console.log(`Last seen online: ${lastOnline.timestamp}`);
// Last seen online: 2024-01-02 09:00

findLastIndex() - 从后往前查找满足条件的元素索引

findLastIndex() 也是 ES2023 新增的方法,返回最后一个满足条件的元素的索引。

javascript
let scores = [
  { player: "Alice", score: 150 },
  { player: "Bob", score: 200 },
  { player: "Charlie", score: 180 },
  { player: "David", score: 200 },
  { player: "Emma", score: 175 },
];

// 查找最后一个得分为 200 的玩家索引
let index = scores.findLastIndex((s) => s.score === 200);
console.log(index); // 3
console.log(scores[index].player); // "David"

// 对比 findIndex() - 找到第一个
let firstIndex = scores.findIndex((s) => s.score === 200);
console.log(firstIndex); // 1
console.log(scores[firstIndex].player); // "Bob"

实际应用:查找最后一个错误日志

javascript
function findLastError(logs) {
  let errorIndex = logs.findLastIndex((log) => log.level === "error");

  if (errorIndex === -1) {
    return null;
  }

  return {
    error: logs[errorIndex],
    position: errorIndex,
    remainingLogs: logs.length - errorIndex - 1,
  };
}

let systemLogs = [
  { level: "info", message: "System started" },
  { level: "warning", message: "High memory usage" },
  { level: "error", message: "Connection failed" },
  { level: "info", message: "Retrying connection" },
  { level: "error", message: "Timeout exceeded" },
  { level: "info", message: "Connection restored" },
];

let lastError = findLastError(systemLogs);
console.log(lastError);
// {
//   error: { level: "error", message: "Timeout exceeded" },
//   position: 4,
//   remainingLogs: 1
// }

方法对比与选择指南

不同的搜索方法适用于不同的场景。这里是一个快速参考:

javascript
let data = [10, 20, 30, 40, 30, 50];

// 1. 查找特定值的位置
console.log(data.indexOf(30)); // 2 - 第一个 30 的索引
console.log(data.lastIndexOf(30)); // 4 - 最后一个 30 的索引

// 2. 检查值是否存在
console.log(data.includes(30)); // true

// 3. 根据条件查找元素
let firstLarge = data.find((n) => n > 25);
console.log(firstLarge); // 30

let lastLarge = data.findLast((n) => n > 25);
console.log(lastLarge); // 50

// 4. 根据条件查找索引
let firstLargeIndex = data.findIndex((n) => n > 25);
console.log(firstLargeIndex); // 2

let lastLargeIndex = data.findLastIndex((n) => n > 25);
console.log(lastLargeIndex); // 5

选择建议

使用 indexOf() / lastIndexOf() 当:

  • 需要查找简单值(字符串、数字、布尔值)的位置
  • 需要查找特定对象引用的位置
  • 需要查找所有匹配项的位置

使用 includes() 当:

  • 只需要知道值是否存在(不关心位置)
  • 需要处理 NaN
  • 代码可读性很重要

使用 find() / findLast() 当:

  • 需要根据复杂条件查找对象
  • 需要找到元素本身而不是索引
  • 需要找第一个或最后一个匹配项

使用 findIndex() / findLastIndex() 当:

  • 需要根据复杂条件查找元素位置
  • 需要基于位置进行后续操作(修改、删除)
  • 需要处理对象数组

性能考虑

时间复杂度

所有这些搜索方法的时间复杂度都是 O(n),即线性时间。这意味着在最坏情况下,它们需要检查数组中的每个元素。

javascript
// 大数组搜索示例
let largeArray = Array.from({ length: 1000000 }, (_, i) => i);

console.time("indexOf");
largeArray.indexOf(999999); // 最坏情况:元素在末尾
console.timeEnd("indexOf"); // 大约几毫秒

console.time("includes");
largeArray.includes(999999);
console.timeEnd("includes"); // 性能相似

console.time("find");
largeArray.find((x) => x === 999999);
console.timeEnd("find"); // 稍慢,因为函数调用开销

优化建议

如果需要频繁搜索大型数组,考虑以下优化:

javascript
// 1. 使用 Set 进行快速查找(O(1) 时间复杂度)
let largeArray = [
  /* 大量数据 */
];
let dataSet = new Set(largeArray);

// Set.has() 比 array.includes() 快得多
console.log(dataSet.has(someValue)); // O(1) 时间

// 2. 使用 Map 存储对象(通过键快速访问)
let users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  // ... 数千个用户
];

// 创建 Map 索引
let userMap = new Map(users.map((user) => [user.id, user]));

// 快速查找用户
let user = userMap.get(1); // O(1) 时间,而不是 O(n)

// 3. 提前终止搜索
let found = data.find((item) => {
  // 一旦找到就立即返回,不继续遍历
  return item.value > threshold;
});

实战案例:电商产品过滤器

让我们综合运用这些搜索方法构建一个产品过滤系统:

javascript
class ProductFilter {
  constructor(products) {
    this.products = products;
  }

  // 检查产品是否在库存中
  isInStock(productId) {
    return this.products.some((p) => p.id === productId && p.stock > 0);
  }

  // 查找特定产品
  findProduct(productId) {
    return this.products.find((p) => p.id === productId);
  }

  // 查找价格范围内的第一个产品
  findInPriceRange(minPrice, maxPrice) {
    return this.products.find(
      (p) => p.price >= minPrice && p.price <= maxPrice
    );
  }

  // 查找特定品牌的所有产品索引
  findBrandIndices(brand) {
    let indices = [];
    this.products.forEach((product, index) => {
      if (product.brand === brand) {
        indices.push(index);
      }
    });
    return indices;
  }

  // 查找评分最高的产品
  findTopRatedInCategory(category) {
    let categoryProducts = this.products.filter((p) => p.category === category);
    let maxRating = Math.max(...categoryProducts.map((p) => p.rating));
    return categoryProducts.findLast((p) => p.rating === maxRating);
  }

  // 检查是否有特定标签的产品
  hasProductsWithTag(tag) {
    return this.products.some((p) => p.tags.includes(tag));
  }
}

// 使用示例
let products = [
  {
    id: 1,
    name: "Gaming Laptop",
    brand: "TechPro",
    category: "Electronics",
    price: 1299,
    stock: 5,
    rating: 4.5,
    tags: ["gaming", "laptop", "high-performance"],
  },
  {
    id: 2,
    name: "Wireless Mouse",
    brand: "TechPro",
    category: "Accessories",
    price: 29,
    stock: 50,
    rating: 4.2,
    tags: ["mouse", "wireless", "accessories"],
  },
  {
    id: 3,
    name: "Mechanical Keyboard",
    brand: "KeyMaster",
    category: "Accessories",
    price: 89,
    stock: 0,
    rating: 4.7,
    tags: ["keyboard", "mechanical", "rgb"],
  },
  {
    id: 4,
    name: "4K Monitor",
    brand: "ViewMax",
    category: "Electronics",
    price: 399,
    stock: 12,
    rating: 4.8,
    tags: ["monitor", "4k", "display"],
  },
];

let filter = new ProductFilter(products);

console.log(filter.isInStock(1)); // true
console.log(filter.isInStock(3)); // false (库存为 0)

console.log(filter.findProduct(2));
// { id: 2, name: "Wireless Mouse", ... }

console.log(filter.findInPriceRange(50, 100));
// { id: 3, name: "Mechanical Keyboard", ... }

console.log(filter.findBrandIndices("TechPro"));
// [0, 1]

console.log(filter.findTopRatedInCategory("Electronics"));
// { id: 4, name: "4K Monitor", rating: 4.8, ... }

console.log(filter.hasProductsWithTag("gaming")); // true
console.log(filter.hasProductsWithTag("bluetooth")); // false

常见陷阱与最佳实践

1. indexOf 与对象比较

javascript
// ❌ 错误:indexOf 无法找到内容相同但引用不同的对象
let users = [{ id: 1, name: "Alice" }];
console.log(users.indexOf({ id: 1, name: "Alice" })); // -1

// ✅ 正确:使用 findIndex
console.log(users.findIndex((u) => u.id === 1 && u.name === "Alice")); // 0

2. includes 与 indexOf 的 NaN 处理

javascript
let values = [1, 2, NaN, 4];

// ❌ indexOf 找不到 NaN
console.log(values.indexOf(NaN) !== -1); // false

// ✅ includes 可以
console.log(values.includes(NaN)); // true

3. 空数组和 undefined 处理

javascript
let emptyArray = [];

// find() 找不到返回 undefined
let result = emptyArray.find((x) => x > 0);
console.log(result); // undefined

// 安全的检查方式
if (result !== undefined) {
  console.log("Found:", result);
} else {
  console.log("Not found");
}

// findIndex() 找不到返回 -1
let index = emptyArray.findIndex((x) => x > 0);
if (index !== -1) {
  console.log("Found at index:", index);
}

4. 性能优化:避免不必要的遍历

javascript
// ❌ 低效:每次都遍历整个数组
function processItems(items, targetIds) {
  return targetIds.map((id) => items.find((item) => item.id === id));
}

// ✅ 高效:创建一次索引
function processItemsOptimized(items, targetIds) {
  let itemMap = new Map(items.map((item) => [item.id, item]));
  return targetIds.map((id) => itemMap.get(id));
}

总结

JavaScript 数组搜索方法为我们提供了灵活而强大的工具来查找和定位数据:

  • indexOf() / lastIndexOf() - 查找简单值的位置,从前或从后开始
  • includes() - 简洁地检查元素是否存在,能正确处理 NaN
  • find() / findLast() - 根据复杂条件查找元素本身
  • findIndex() / findLastIndex() - 根据复杂条件查找元素位置

选择合适的方法取决于你的具体需求:是查找简单值还是复杂对象?需要位置还是元素本身?从前往后还是从后往前?理解每个方法的特点和性能特性,能帮助你写出更高效、更易读的代码。

在处理大型数据集时,记得考虑性能优化策略,如使用 Map 或 Set 来实现更快的查找。搜索方法是数组操作的基础,熟练掌握它们将为你处理各种数据查找场景打下坚实的基础。