JavaScript的Array.prototype.indexOf方法是什么?如何使用?

1.indexof方法用于查找数组中元素的首次出现位置,返回索引或-1。2.语法为arr.indexof(searchelement[, fromindex]),其中searchelement是要查找的元素,fromindex是可选起始位置,默认从0开始,负数则从Array.Length + fromindex计算。3.返回值为首次匹配的索引或-1,比较时使用严格相等(===),因此不适用于对象内容比较。4.与includes的区别在于indexof返回索引而includes返回布尔值,前者适合需索引操作的场景,后者适合仅需判断存在的场景。5.indexof无法基于对象属性查找,此时应使用findindex或find方法。6.indexof性能为o(n),适用于小数组,大数组或频繁查找可用set或二分查找(需排序)。

JavaScript的Array.prototype.indexOf方法是什么?如何使用?

JavaScript的Array.prototype.indexOf方法是一个数组的内置功能,它的主要作用是帮助你查找一个给定元素在数组中首次出现的位置。简单来说,就是告诉你“这个东西在数组的第几个位置?”如果找到了,它会返回该元素对应的索引值(一个数字,从0开始计数);如果没找到,它就会返回-1。这是在JavaScript中进行数组元素查找的一个非常基础且常用的工具

JavaScript的Array.prototype.indexOf方法是什么?如何使用?

直接输出解决方案即可

要使用indexOf,它的基本语法是这样的:arr.indexOf(searchElement[, fromIndex])。

立即学习Java免费学习笔记(深入)”;

JavaScript的Array.prototype.indexOf方法是什么?如何使用?

  • searchElement:这是你想要在数组中查找的那个元素。它可以是任何类型的值,比如字符串、数字、布尔值,甚至是NULLundefined
  • fromIndex:这是一个可选参数。如果你提供了它,indexOf就会从这个索引位置开始向后搜索。如果你不提供,它默认会从数组的第一个元素(索引0)开始搜索。
    • 如果fromIndex是一个负数,它会被当作array.length + fromIndex来计算。举个例子,如果数组有5个元素,fromIndex是-2,那么搜索就会从索引3(5 + -2)开始。
    • 如果计算出来的起始索引超出了数组的范围(比如比数组长度还大),它会从数组末尾开始搜索,结果通常是找不到。

它的返回值很明确:

  • 如果找到了searchElement,就返回它在数组中首次出现的索引。
  • 如果没找到,就返回-1。

来看几个例子,这样会更清楚:

JavaScript的Array.prototype.indexOf方法是什么?如何使用?

const fruits = ['apple', 'banana', 'orange', 'apple', 'grape'];  // 查找 'banana' 的位置 const index1 = fruits.indexOf('banana'); console.log(index1); // 输出: 1 (因为 'banana' 在索引1的位置)  // 查找一个不存在的元素 'kiwi' const index2 = fruits.indexOf('kiwi'); console.log(index2); // 输出: -1 (因为 'kiwi' 不在数组中)  // 从索引2开始查找 'apple' // 数组是 ['apple', 'banana', 'orange', 'apple', 'grape'] // 从 'orange' (索引2) 开始往后找,下一个 'apple' 在索引3 const index3 = fruits.indexOf('apple', 2); console.log(index3); // 输出: 3  // 从倒数第三个位置开始查找 'apple' // 数组长度是5,倒数第三个位置是 5 - 3 = 2 (即 'orange') const index4 = fruits.indexOf('apple', -3); console.log(index4); // 输出: 3 (和上面一样,都是从索引2开始找)  // 查找 null 或 undefined const mixedArray = [1, null, 3, undefined, 5]; console.log(mixedArray.indexOf(null));      // 输出: 1 console.log(mixedArray.indexOf(undefined)); // 输出: 3

需要特别注意的是,indexOf在比较元素时使用的是严格相等(===)。这意味着它不仅比较值,还比较类型。比如,数字5和字符串”5″在indexOf看来是不同的。

indexOf 和 includes 有什么区别?什么时候用哪个?

这确实是很多人会混淆的地方,因为它们都涉及“查找”这个动作。但它们的核心区别在于“返回什么”以及“你真正想知道什么”。

indexOf,我们刚才已经详细说了,它返回的是元素在数组中的索引(一个数字),或者-1。它的目的是告诉你“这个东西在哪里?”或者“它在不在,如果在,具体在哪?”

而Array.prototype.includes()方法则简单粗暴得多。它只返回一个布尔值(true或false)。它的目的就是回答一个简单的“是或否”问题:“这个数组里有没有这个东西?”

举个例子,就像你问一个朋友:“我的钥匙在哪里?”他可能会说:“在桌子上”(这就是indexOf,告诉你具体位置)。但如果你问他:“我的钥匙在家里吗?”他只需要回答“是”或“否”(这就是includes,只关心存在与否)。

那么,什么时候用哪个呢?

  • 使用 indexOf 的场景:

    • 当你不仅想知道一个元素是否存在,还想知道它具体在哪个位置时。
    • 当你需要根据元素的位置来执行后续操作时,比如使用splice()方法来删除或插入某个位置的元素。
    • 当你需要处理数组中重复元素的情况,并且想找到第一个出现的那个时。
    • 一个常见的模式是 if (arr.indexOf(element) !== -1),这表示如果元素存在,就执行某些操作。
  • 使用 includes 的场景:

    • 当你只关心数组中是否存在某个元素,而不需要知道它具体在哪个位置时。
    • 当你的代码逻辑只需要一个简单的真/假判断,而不需要索引值时,includes能让你的代码更简洁、更易读。
    • 我个人在编写代码时,如果只是为了判断某个值是否存在,通常会优先选择includes,因为它表达的意图非常清晰,一眼就能看出是“包含检查”。
const items = ['pen', 'notebook', 'eraser'];  // 场景1:我需要知道 "notebook" 在哪里,以便后续操作 const notebookIndex = items.indexOf('notebook'); if (notebookIndex !== -1) {     console.log(`找到了笔记本,在索引 ${notebookIndex}。`);     // 假设我要把笔记本换成新的     items.splice(notebookIndex, 1, 'new notebook');     console.log(items); // 输出: ['pen', 'new notebook', 'eraser'] }  // 场景2:我只关心 "eraser" 有没有在列表里 if (items.includes('eraser')) {     console.log('列表里有橡皮擦。'); } else {     console.log('列表里没有橡皮擦。'); }

总的来说,选择哪个方法取决于你的具体需求。如果你只需要一个布尔值来判断存在性,includes更简洁;如果你需要元素的具体位置,indexOf则是你的首选。

indexOf 在处理复杂数据类型时有什么限制?

这是indexOf一个非常重要的“坑”,或者说,是它的设计哲学所带来的限制。理解这一点对于避免一些难以发现的bug至关重要。

核心限制在于:indexOf在比较元素时,使用的是严格相等(===)

这意味着什么呢? 对于原始数据类型(如数字、字符串、布尔值、null、undefined),indexOf工作得非常好,因为它能准确地比较它们的值和类型。

但是,对于复杂数据类型,也就是对象(包括普通对象、数组、函数等),indexOf不会去比较它们内部的属性值是否相同,它只会比较它们在内存中的引用地址是否相同。

举个例子你就明白了:

const users = [     { id: 1, name: 'Alice' },     { id: 2, name: 'Bob' },     { id: 3, name: 'Charlie' } ];  // 尝试查找一个与数组中第一个对象“看起来一样”的新对象 const searchUser1 = { id: 1, name: 'Alice' }; const foundIndex1 = users.indexOf(searchUser1); console.log(foundIndex1); // 输出: -1  // 为什么是 -1? // 因为 searchUser1 是一个全新的对象,虽然它的内容和 users[0] 一模一样, // 但它们在内存中是两个不同的对象实例,引用地址不同。 // indexOf 认为它们是不同的。  // 那么,如果我查找数组中已有的对象引用呢? const existingUser = users[0]; // 拿到数组中第一个对象的引用 const foundIndex2 = users.indexOf(existingUser); console.log(foundIndex2); // 输出: 0  // 这次是 0。因为 existingUser 和 users[0] 指向的是内存中的同一个对象。

这个限制在处理由后端API返回的数据时尤其常见。你从API拿到的可能是[{ id: 1, name: ‘Alice’ }],然后你本地又构造了一个{ id: 1, name: ‘Alice’ },你期望indexOf能找到它,但结果往往是-1。

解决方案: 当你需要根据复杂对象的内容(例如,某个属性的值)来查找时,indexOf就无能为力了。这时,你需要使用其他方法,最常用的是:

  1. Array.prototype.findIndex(): 这个方法接受一个回调函数作为参数。你可以在回调函数中定义你的查找逻辑,比如比较对象的某个属性。它会返回满足条件的第一个元素的索引,如果没找到则返回-1。

    const foundIndexById = users.findIndex(user => user.id === 2); console.log(foundIndexById); // 输出: 1 (找到了 id 为 2 的 Bob)  const foundIndexByName = users.findIndex(user => user.name === 'Charlie'); console.log(foundIndexByName); // 输出: 2
  2. Array.prototype.find(): 类似findIndex,但它返回的是满足条件的第一个元素本身,而不是它的索引。如果没找到则返回undefined。

    const foundUser = users.find(user => user.id === 3); console.log(foundUser); // 输出: { id: 3, name: 'Charlie' }

对我个人来说,这个严格相等的问题在初期学习JavaScript时确实让我困惑了很久。你总觉得“明明内容都一样,为什么找不到?”直到深入理解了原始值和引用值的区别,以及===的运作方式,才豁然开朗。所以,记住这个限制非常重要,它能帮你避免很多不必要的调试时间。

indexOf 的性能考量和替代方案

当我们谈论indexOf的性能时,主要指的是它在查找元素时所采用的机制。indexOf本质上执行的是一个线性搜索(或称顺序搜索)。这意味着它会从指定的fromIndex开始,一个接一个地检查数组中的每个元素,直到找到目标元素或遍历完整个数组。

性能考量:

  • 时间复杂度: 在最坏的情况下(目标元素在数组的末尾,或者根本不存在),indexOf需要检查数组中的所有元素。因此,它的时间复杂度是O(n),其中n是数组的长度。这意味着数组越大,查找所需的时间就可能越长。
  • 适用场景: 对于小型数组(比如几十个或几百个元素),O(n)的性能通常可以忽略不计,indexOf用起来非常方便且足够快。但在处理非常大的数组(比如几万、几十万甚至上百万个元素)时,或者在性能敏感的应用中需要频繁地进行查找操作时,O(n)的线性搜索就可能成为一个性能瓶颈。

替代方案(在特定场景下可以提供更好的性能或功能):

  1. Array.prototype.findIndex() / Array.prototype.find():

    • 何时使用: 当你需要查找复杂数据类型(对象)时,或者需要根据更复杂的条件(而不仅仅是严格相等)来查找元素时。
    • 性能: 它们同样执行线性搜索,所以时间复杂度也是O(n)。在性能上,它们和indexOf属于同一量级。但它们提供了更灵活的查找逻辑,可以弥补indexOf在复杂类型查找上的不足。
  2. 使用 Set 进行存在性检查:

    • 何时使用: 如果你的主要需求是频繁地检查一个元素是否存在于一个大型集合中,并且这个集合中的元素是唯一的。
    • 性能: Set是一种专门用于存储唯一值的集合。向Set中添加元素(构建Set)的时间复杂度是O(n)。但是,一旦Set构建完成,使用Set.prototype.has()方法来检查元素是否存在,平均时间复杂度可以达到惊人的O(1)(常数时间)!这意味着无论Set有多大,查找一个元素所需的时间理论上都是固定的。
    • 局限: Set只能存储原始值或对象的引用,同样不能进行基于对象内容(深比较)的查找。而且,构建Set本身需要时间,所以只有当你需要对同一个数据集进行多次查找时,Set的性能优势才能体现出来。
       const largeArray = Array.from({ length: 100000 }, (_, i) => `item-${i}`); const itemToSearch = 'item-99999'; // 查找最后一个元素

    console.time(‘indexOf Search’); largeArray.indexOf(itemToSearch); console.timeEnd(‘indexOf Search’); // 对于大数组,这里可能会耗时

    // 转换为 Set console.time(‘Set Creation’); const itemSet = new Set(largeArray); console.timeEnd(‘Set Creation’); // 创建 Set 需要 O(n) 时间

    console.time(‘Set.has Search’); itemSet.has(itemToSearch); console.timeEnd(‘Set.has Search’); // 查找非常快

     你会发现,对于单次查找,`indexOf`可能看起来更快(因为它不需要额外的Set构建时间),但如果进行成百上千次查找,`Set.has()`的累计优势会非常明显。
  3. 二分查找(Binary Search):

    • 何时使用: 当你的数组是已排序的,并且你需要高效地查找元素时。
    • 性能: 二分查找的时间复杂度是O(log n)。这是一个巨大的性能提升,尤其对于非常大的数组。例如,一个包含一百万个元素的数组,线性搜索可能需要一百万步,而二分查找只需要大约20步(log2(1,000,000) ≈ 19.9)。
    • 局限: JavaScript原生并没有内置的二分查找方法。你需要自己实现它,或者引入一个工具库。更重要的是,它要求数组必须是已排序的。如果数组未排序,你需要先对其进行排序(排序本身通常是O(n log n)),这可能会抵消查找的性能优势。

我的经验是,不要过度优化。在绝大多数日常开发场景中,indexOf的性能是完全足够的。只有当你遇到明显的性能瓶颈,并且通过性能分析工具(如浏览器开发者工具)确认是数组查找导致的问题时,才应该考虑上述的替代方案。选择“对的工具”而不是“最快的工具”往往能带来更好的开发效率和代码可读性。indexOf就像你的瑞士军刀,虽然不是每个任务都最锋利,但它足够通用且可靠。

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享