ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

现代 JavaScript 教程:数组方法全解析——增删、搜索、遍历与转换实战指南

现代 JavaScript 教程:数组方法全解析——增删、搜索、遍历与转换实战指南 文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载数组是 JavaScript 中最常用的数据结构之一而Array.prototype上挂载的方法多达数十种。本篇以《现代 JavaScript 教程》中 数组方法 一章为骨架系统讲解增删元素splice/slice/concat、遍历forEach、搜索indexOf/find/filter、转换map/sort/split/join/reduce等核心方法并结合本仓库 05-array-methods 目录下 12 道配套习题与参考实现帮助你把每一个方法用对、用活。读完本文你将掌握一份可随时查阅的数组方法备忘单并具备独立解决字符串转驼峰、范围过滤、数组去重、按字段排序、按 id 分组等日常开发问题的能力。添加/移除数组元素数组的四把入门工具我们在前面的章节已经接触过这里先复习arr.push(...items)—— 从尾端添加元素arr.pop()—— 从尾端提取删除并返回元素arr.shift()—— 从首端提取元素arr.unshift(...items)—— 从首端添加元素。它们只操作数组两端。如果你需要在任意位置增删元素就需要下面几个进阶方法。splice数组的瑞士军刀直接使用delete删除数组元素会留下空洞let arr [I, go, home]; delete arr[1]; // 删除 go alert( arr[1] ); // undefined alert( arr.length ); // 3元素还在只是变空了原因在于delete obj.key只是移除键对应的值数组作为对象并不会因此收缩长度。想要让后面的元素前移、得到一个更短的数组必须使用专门的方法——arr.splice正是为此而生它可以删除、插入、替换一气呵成因此常被称为数组方法中的瑞士军刀。语法arr.splice(start[, deleteCount, elem1, ..., elemN])它从索引start开始修改arr删除deleteCount个元素并在当前位置插入elem1, ..., elemN最后返回被删除元素组成的数组。看几个实例// 1) 从索引 1 开始删除 1 个元素 let arr [I, study, JavaScript]; arr.splice(1, 1); alert( arr ); // [I, JavaScript] // 2) 删除前 3 个元素并用另外两个元素替换它们 let arr2 [I, study, JavaScript, right, now]; arr2.splice(0, 3, Lets, dance); alert( arr2 ); // [Lets, dance, right, now] // 3) 返回值是被删除的元素组成的数组 let arr3 [I, study, JavaScript, right, now]; let removed arr3.splice(0, 2); alert( removed ); // I, study // 4) deleteCount 为 0 时只插入不删除 let arr4 [I, study, JavaScript]; arr4.splice(2, 0, complex, language); alert( arr4 ); // I, study, complex, language, JavaScript负向索引splice及本章多数方法都允许负索引从数组末尾倒数计算位置。例如arr.splice(-1, 0, 3, 4)表示从倒数第 1 位尾端前一位开始删除 0 个元素插入 3 和 4结果是[1, 2, 3, 4, 5]。实战佐证习题 3-filter-range-in-place 要求原地删除范围之外的元素其 参考实现 正是利用splice删除后回退下标i--避免跳过元素function filterRangeInPlace(arr, a, b) { for (let i 0; i arr.length; i) { let val arr[i]; if (val a || val b) { // 超出 [a, b] 范围则删除 arr.splice(i, 1); i--; // 删除后索引前移回退一位再检查 } } } let arr [5, 3, 8, 1]; filterRangeInPlace(arr, 1, 4); alert( arr ); // [3, 1]注意该函数只修改数组、不返回任何东西这是原位操作类 API 的典型约定。slice复制子数组arr.slice([start], [end])比splice简单得多它返回一个新数组包含从索引start到end不含end的所有元素。start和end都可以是负数从末尾计数。它和字符串的str.slice用法神似只不过操作对象是数组。let arr [t, e, s, t]; alert( arr.slice(1, 3) ); // e,s复制位置 1 到 3不含 3 alert( arr.slice(-2) ); // s,t复制倒数第 2 位到尾端不带参数调用arr.slice()会得到整个数组的浅拷贝这是先复制再转换、不动原数组模式的常用起点。concat拼接数组与其他值arr.concat(arg1, arg2...)接受任意数量的参数数组或单个值返回一个新数组先是arr的所有元素随后依次是各参数的元素参数为数组时展开其元素否则直接追加参数本身。let arr [1, 2]; alert( arr.concat([3, 4]) ); // 1,2,3,4 alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6类数组对象的特殊行为默认情况下普通对象即使带length属性如{0: something, length: 1}也会被当作单个整体追加进去得到1,2,[object Object]。但如果该对象带有Symbol.isConcatSpreadable属性且值为true它就会被concat当作数组展开let arrayLike { 0: something, 1: else, [Symbol.isConcatSpreadable]: true, length: 2 }; alert( arr.concat(arrayLike) ); // 1,2,something,else遍历forEacharr.forEach(function(item, index, array) {...})为数组的每个元素调用一次函数最常用于只遍历、不求返回值的场景[Bilbo, Gandalf, Nazgul].forEach(alert);回调可以拿到三个参数——当前元素item、索引index、数组本身array[Bilbo, Gandalf, Nazgul].forEach((item, index, array) { alert(${item} is at index ${index} in ${array}); });回调的返回值会被forEach忽略如果你需要在遍历过程中产出新值请改用后面的map。在数组中搜索indexOf / lastIndexOf / includes三者与字符串同名方法行为一致只是作用对象是数组元素arr.indexOf(item, from)—— 从索引from开始搜索item找到返回索引否则返回-1arr.lastIndexOf(item, from)—— 与indexOf相同但从右向左查找arr.includes(item, from)—— 从索引from开始搜索找到返回true否则返回false。let arr [1, 0, false]; alert( arr.indexOf(0) ); // 1 alert( arr.indexOf(false) ); // 2 alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true let fruits [Apple, Orange, Apple]; alert( fruits.indexOf(Apple) ); // 0第一个 Apple alert( fruits.lastIndexOf(Apple) ); // 2最后一个 Apple两个注意点使用严格相等比较搜索false只会命中布尔false不会命中数字0。includes能正确处理NaN而indexOf不能const arr [NaN]; alert( arr.indexOf(NaN) ); // -1错误应为 0 alert( arr.includes(NaN) ); // true正确这是因为includes较晚加入 JavaScript内部采用了更新、更完善的比较算法。只关心是否存在、不关心索引时includes是首选。实战佐证习题 11-array-unique数组去重的 参考实现 正是用includes判断元素是否已存在于结果集function unique(arr) { let result []; for (let str of arr) { if (!result.includes(str)) { result.push(str); } } return result; } let strings [Hare, Krishna, Hare, Krishna, Krishna, Krishna, Hare, Hare, :-O]; alert( unique(strings) ); // Hare, Krishna, :-O该解法正确但存在性能隐患includes内部要线性扫描result若arr有 10000 个元素最坏会达到 1 亿次比较仅适合小型数组。更优方案利用Set会在本教程 Map 和 Set 一章中给出。find / findIndex / findLastIndex按条件查找对象数组在实际开发中极其常见这时需要按条件而非按值查找。arr.find依次对每个元素调用回调let result arr.find(function(item, index, array) { // 返回 true 则停止迭代并返回 item全部为假则返回 undefined });回调参数item元素、index索引、array数组本身。找到第一个满足条件的元素即返回否则返回undefined。let users [ {id: 1, name: John}, {id: 2, name: Pete}, {id: 3, name: Mary} ]; let user users.find(item item.id 1); alert(user.name); // John配套方法arr.findIndex—— 语法相同但返回索引而非元素未找到返回-1arr.findLastIndex—— 与findIndex相同但从右向左搜索类似lastIndexOf。let users [ {id: 1, name: John}, {id: 2, name: Pete}, {id: 3, name: Mary}, {id: 4, name: John} ]; alert(users.findIndex(user user.name John)); // 0第一个 John alert(users.findLastIndex(user user.name John)); // 3最后一个 Johnfilter筛选所有匹配项find只返回第一个匹配元素要拿到所有匹配元素组成的数组使用arr.filter(fn)。语法与find类似但回调返回true时元素被收集进结果数组迭代继续没有匹配时返回空数组。let users [ {id: 1, name: John}, {id: 2, name: Pete}, {id: 3, name: Mary} ]; let someUsers users.filter(item item.id 3); alert(someUsers.length); // 2实战佐证习题 2-filter-range过滤范围要求返回新数组且不修改原数组其 参考实现 一行搞定function filterRange(arr, a, b) { return arr.filter(item (a item item b)); } let arr [5, 3, 8, 1]; let filtered filterRange(arr, 1, 4); alert( filtered ); // 3,1匹配的值 alert( arr ); // 5,3,8,1原数组未被改动对比前面用splice的filterRangeInPlace可以清晰看到返回新数组filter与原位修改splice两种编程风格的分野。转换数组map一对一映射arr.map是最常用、最有用的方法之一对每个元素调用回调收集返回值组成新数组原数组不变。let result arr.map(function(item, index, array) { // 返回新值替换当前元素 });let lengths [Bilbo, Gandalf, Nazgul].map(item item.length); alert(lengths); // 5,7,6实战佐证习题 1-camelcase将border-left-width转为borderLeftWidth是split → map → join三步流的教科书案例其 参考实现 为function camelize(str) { return str .split(-) // my-long-word - [my, long, word] .map( // 除第一项外其余各项首字母大写 (word, index) index 0 ? word : word[0].toUpperCase() word.slice(1) ) .join(); // [my, Long, Word] - myLongWord } camelize(background-color) backgroundColor; // true camelize(list-style-image) listStyleImage; // true camelize(-webkit-transition) WebkitTransition; // truesort(fn)原位排序arr.sort()对数组进行原位in-place排序——直接改变arr自身顺序并返回排序后的数组返回值通常被忽略。关键陷阱默认按字符串的词典顺序排序let arr [ 1, 2, 15 ]; arr.sort(); alert( arr ); // 1, 15, 2字符串比较下 2 15要按自己的规则排序需要传入比较函数compare(a, b)它返回function compare(a, b) { if (a b) return 1; // 第一个值更大 if (a b) return 0; // 相等 if (a b) return -1; // 第一个值更小 }按数字升序排序let arr [ 1, 2, 15 ]; arr.sort((a, b) a - b); alert(arr); // 1, 2, 15关于sort的几个要点比较函数只需返回正数/负数/零不必严格返回 ±1。因此(a, b) a - b是最简洁的数字升序写法箭头函数版本与完整函数完全等价回顾 箭头函数基础。内部采用通用排序算法多数引擎实现为快速排序或 Timsort 的优化版本会尽可能减少比较次数可以通过在比较函数中alert观察算法实际比较了哪些元素。字符串排序建议用localeCompare默认字符串比较按字符编码进行对Ö这类字母会出错。用str.localeCompare才能得到符合语言习惯的顺序let countries [Österreich, Andorra, Vietnam]; alert( countries.sort( (a, b) a b ? 1 : -1) ); // Andorra, Vietnam, Österreich错的 alert( countries.sort( (a, b) a.localeCompare(b) ) ); // Andorra, Österreich, Vietnam对的关于字符串比较算法详见 字符串 一章。实战佐证习题 8-sort-objects按年龄对用户排序演示了对象数组 sort的标准组合let john { name: John, age: 25 }; let pete { name: Pete, age: 30 }; let mary { name: Mary, age: 28 }; let arr [ pete, john, mary ]; arr.sort((a, b) a.age - b.age); alert(arr[0].name); // John25 岁 alert(arr[1].name); // Mary28 岁 alert(arr[2].name); // Pete30 岁reverse原地反转arr.reverse()颠倒arr中元素的顺序原位操作并返回反转后的数组let arr [1, 2, 3, 4, 5]; arr.reverse(); alert( arr ); // 5,4,3,2,1split 和 join字符串 ⇄ 数组想象一个真实场景消息应用里用户输入了逗号分隔的收件人列表John, Pete, Mary而业务逻辑需要的是名字数组。str.split(delim)通过分隔符delim把字符串切分成数组let names Bilbo, Gandalf, Nazgul; let arr names.split(, ); for (let name of arr) { alert( A message to ${name}. ); // A message to Bilbo依次类推 }split还有可选的第二个数字参数限制结果数组长度超出部分被忽略实际使用较少let arr Bilbo, Gandalf, Nazgul, Saruman.split(, , 2); alert(arr); // Bilbo, Gandalf用空字符串split()可以把字符串拆成字符数组let str test; alert( str.split() ); // t,e,s,tarr.join(glue)是split的逆操作用glue把数组项粘合成字符串let arr [Bilbo, Gandalf, Nazgul]; let str arr.join(;); alert( str ); // Bilbo;Gandalf;Nazgul实战联动上面camelize习题正是split(-) → map → join()的完整链条可见这三个方法经常组合出招。reduce / reduceRight归约为单个值当需要遍历并返回每个元素的数据时用map当需要根据整个数组计算单个值时用arr.reduce。语法let value arr.reduce(function(accumulator, item, index, array) { // ... }, [initial]);回调被逐个应用到每个元素并把结果搬运到下一次调用accumulator—— 上一次调用的结果第一次等于initial若提供item—— 当前元素index—— 当前索引array—— 数组本身。求数组总和let arr [1, 2, 3, 4, 5]; let result arr.reduce((sum, current) sum current, 0); alert(result); // 15执行过程初始值0作为第一次调用的sum调用次数sumcurrentresult第 1 次011第 2 次123第 3 次336第 4 次6410第 5 次10515也可以省略初始值此时reduce把数组第一个元素当作初始值从第二个元素开始迭代结果同样为15。但空数组 无初始值会直接抛错TypeError: Reduce of empty array with no initial value因此实践上建议始终显式指定初始值。arr.reduceRight与reduce完全一致只是遍历方向为从右到左。实战佐证习题 12-reduce-object以id为键把用户数组转成对象是reduce的高级用法——累加器不是数字而是对象其 参考实现 为function groupById(array) { return array.reduce((obj, value) { obj[value.id] value; // 以 id 为键数组项为值 return obj; // 必须返回累加器供下一次调用使用 }, {}) // 初始值必须是空对象 } let users [ {id: john, name: John Smith, age: 20}, {id: ann, name: Ann Smith, age: 24}, {id: pete, name: Pete Peterson, age: 31}, ]; let usersById groupById(users); // usersById { john: {...}, ann: {...}, pete: {...} }处理服务端返回的用户列表时这种数组 → 以 id 索引的对象转换能极大简化后续查找。该任务假设id唯一若可能出现重复键需要先约定合并策略。Array.isArray识别数组数组基于对象实现并不构成独立的语言类型所以typeof无法区分数组和普通对象alert(typeof {}); // object alert(typeof []); // object相同判断是否为数组要用专门方法Array.isArray(value)alert(Array.isArray({})); // false alert(Array.isArray([])); // true大多数方法都支持 thisArg几乎所有调用函数的数组方法——find、filter、map等sort是例外——都接受一个可选的最后一个参数thisArg它的值会成为回调内部的thisarr.find(func, thisArg); arr.filter(func, thisArg); arr.map(func, thisArg);典型用法把对象方法直接当作回调同时保持其this指向let army { minAge: 18, maxAge: 27, canJoin(user) { return user.age this.minAge user.age this.maxAge; } }; let users [ {age: 16}, {age: 20}, {age: 23}, {age: 30} ]; // 找到 army.canJoin 返回 true 的 user let soldiers users.filter(army.canJoin, army); alert(soldiers.length); // 2 alert(soldiers[0].age); // 20 alert(soldiers[1].age); // 23若写成users.filter(army.canJoin)canJoin会被当作独立函数调用此时this undefined访问this.minAge会立即报错。等效且更易读的写法是users.filter(user army.canJoin(user))——实践中后者更常见因为箭头函数天然捕获外层this无需显式传thisArg。补充方法some / every / fill / copyWithin / flat / Array.of除上述主力方法外还有几个值得收录的补充方法完整清单可查阅 MDN 的 Array 手册arr.some(fn)/arr.every(fn)—— 对每个元素调用fn若任一/全部结果为真则返回true否则返回false。行为类似||与some遇到真值立即短路返回trueevery遇到假值立即短路返回false。经典应用是比较两个数组function arraysEqual(arr1, arr2) { return arr1.length arr2.length arr1.every((value, index) value arr2[index]); } alert( arraysEqual([1, 2], [1, 2])); // truearr.fill(value, start, end)—— 用value填充从索引start到end的区间原位操作。arr.copyWithin(target, start, end)—— 把从start到end的元素复制到数组自身的target位置覆盖现有元素。arr.flat(depth)/arr.flatMap(fn)—— 从多维数组创建扁平化新数组flatMap等价于map后再flat(1)。Array.of(element0, element1, ...)—— 基于可变数量参数创建新的Array实例不受参数数量或类型限制。总结数组方法备忘单添加/删除元素push(...items)—— 向尾端添加pop()—— 从尾端提取一个shift()—— 从首端提取一个unshift(...items)—— 向首端添加splice(pos, deleteCount, ...items)—— 从pos开始删除deleteCount个元素并插入itemsslice(start, end)—— 返回新数组复制[start, end)区间concat(...items)—— 返回新数组复制当前数组全部元素并追加items数组参数展开元素。搜索元素indexOf/lastIndexOf(item, pos)—— 从pos开始搜索找到返回索引否则返回-1includes(value)—— 存在则返回true可正确处理NaNfind/filter(func)—— 返回使func为真的第一个值 / 所有值findIndex—— 同find但返回索引findLastIndex从右向左找。遍历元素forEach(func)—— 对每个元素调用func无返回值。转换数组map(func)—— 根据回调返回值创建新数组sort(func)—— 原位排序默认按字符串reverse()—— 原位反转split/join—— 字符串拆数组 / 数组拼字符串reduce/reduceRight(func, initial)—— 迭代归约为单个值建议始终给初始值。其他Array.isArray(value)—— 判断是否为数组。请注意sort、reverse和splice修改的是数组本身slice、map、filter、concat、reduce返回新数组/新值不改动原数组。这些方法覆盖了约 99% 的日常用例。记忆方法先浏览上面的备忘单建立印象再动手完成 05-array-methods 目录下的 12 道习题字符串转驼峰、范围过滤与原地过滤、数组去重、按年龄排序、可扩展计算器、按 id 分组等遇到不知道该用什么方法的操作时回到这份备忘单对照示例——用不了太久你就能不假思索地选出正确方法。赞分享文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载相关推荐现代 JavaScript 教程Object.keys / values / entries 遍历与转换实战指南现代 JavaScript 教程Object.keys / values / entries 遍历与转换实战指南 本文以现代 JavaScript 教程zh文档教程前端千古前端图文教程JavaScript 数组的常见方法全解析类型判断、增删改查、遍历与排序千古前端图文教程JavaScript 数组的常见方法全解析类型判断、增删改查、遍历与排序 本篇文章来自「千古前端图文教程」仓库的《JavaScript 基文档教程前端The JavaScript Way 数组实战指南用 Array 存储、访问、遍历与增删数据第 7 章精讲The JavaScript Way 数组实战指南用 Array 存储、访问、遍历与增删数据第 7 章精讲 本文基于开源书籍《The JavaScript教程文档创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表