构思一个电商页面,想从购物车数组里找出第一件价格超过200元的商品;或者在日志列表中快速判断是否出现过某个错误码。打开编辑器,正准备写for循环一行行判断?先别急,JavaScript的数组身上早已备好了一整套查找方法,从简单的值定位到从末尾条件搜索,几行调用就能顶过去几十行手工代码。
这些方法并非一步到位。最早的indexOf和lastIndexOf确立了位置检索的范式,之后ECMAScript 2016引入includes解决了NaN判断的尴尬,再到find和findIndex带来函数式条件查找,直至ES2023补齐了从末尾发起条件搜索的findLast和findLastIndex。沿着这次标准化的时间线复盘,能够清晰看到开发者需求是怎么推着API往前走的。
最先登场的是indexOf。它接受一个要搜索的元素值,从头开始逐一比对,返回该值第一次出现的位置索引。找不到则返回-1。lastIndexOf逻辑和indexOf相同,只是从数组尾部向头部遍历,返回该值最后一次出现的位置。这两个方法足够应付简单的字符串或数字匹配,很快成为数组操作的日常标配。但缺陷也随着使用场景的复杂化暴露出来:不能处理NaN,因为NaN===NaN永远为false,导致无论怎么查找都返回-1;另外,它们只认值的严格相等,无法表达“大于18”这类条件,如果数组里不存在完全相等的值,就无法工作。
转折发生在ECMAScript 2016。那一年数组纳入了includes方法。它同样接收一个元素值,直接返回布尔值,告诉开发者数组里到底有没有这个东西。更关键的变化是,includes能够正确判断NaN的存在。以往使用indexOf检查数组是否包含NaN,总会返回-1,得出数组不包含的假结论;includes则打破了NaN不等于自身的桎梏,让“是否存在NaN”这个需求变得可判断。同时,返回布尔值的语义比返回-1要清晰得多,调用之后不用再和-1做比较,代码的可读性也上了一层。
不过includes依然只能回答“有没有”,却回答不了“哪个元素符合条件”。想要从数组中找出第一个大于18的成员,或者说找出第一个用户名长度超限的字符串,纯等检查已经不够用了。于是,find和findIndex带着测试函数的概念加入进来。find方法接收一个回调函数,遍历数组的每一个元素,一旦回调返回true,就立刻将当前元素的值作为结果返回,不再继续遍历。这个测试函数可以接收三个参数:正在处理的当前元素值、当前元素的索引,以及数组本身。拿找第一个大于18的数字来说,只需要在回调里写一句“element > 18”,find会自动把满足条件的第一个元素值交到你手里。findIndex和find几乎一样,不同的是它返回对应元素的索引,而不是元素值。这样,无论拿到的是值还是位置,都能灵活服务于后续逻辑。
然而,需求总在延伸。如果数组很长,想从最新的记录往前找第一个满足条件的元素,用find只能从头扫到尾,效率上并不理想;用reverse再找虽然也行,但会改变原数组,给自己添乱。ES2023直接补上了这一块:新增的findLast方法会从数组的末尾开始向前遍历,返回第一个满足条件的元素值;findLastIndex则返回这个元素的索引。这两个方法接收同样的测试函数,同样在函数返回true时停止搜索。对于从时间戳倒序查找最新匹配项、或者数组本身就是按优先级由低到高排列却要拿最高优先级的匹配时,findLast和findLastIndex让逻辑和遍历方向对上,代码一下子直白很多。
从indexOf到findLast,七个方法一字排开,围出了一个清晰的查找矩阵。简单值定位,用indexOf或无副作用版本lastIndexOf;只关心存在性、尤其是可能包含NaN的场景,果断换includes;需要按条件精准捞出第一个合格元素或它的索引,find和findIndex正好胜任;反向条件检索则交给findLast和findLastIndex。它们并没有孰优孰劣,只有不同维度下的合适选择。下次再碰到数组查找,先想想这套组合拳,大概率一行方法调用就能替换掉一大串手写的循环判断。
热门跟贴