☰
JavaScript数组API全梳理:分类、返回值与避坑指南
2026/10/14 2:44:34 网站建设 项目流程

在JavaScript里,数组几乎是每天打交道最多的数据结构,没有之一。接口返回的列表要处理、前端要筛选要排序、表格要做统计聚合,各个环节绕不开的一个问题就是:到底该调用哪个数组API更合适。我见过不少刚入门的同学,学会一个forEach就走天下了,循环里套if、套临时变量,代码又臭又长;也有经验丰富的同事,偶尔把map和forEach记混,或者把splice和slice写反,排查半天才发现是自己动了原数组。这篇内容我打算把JS数组常用API从头到尾梳理一遍,按操作意图分类,讲清楚每个API的返回值、副作用和适用场景,再配上几个我实际踩过的坑,希望能帮你把这块基础能力真正焊牢。

1. 数组API很多,先从分类和总览下手

1.1 为什么值得专门花时间梳理数组API

很多人写JS的时候,数组操作用得最多的就是push和forEach。但实际业务场景里,数据处理往往是一连串的管道操作:先筛选掉无效数据,再映射成显示格式,最后做分组统计。如果只会用for循环加一堆临时变量,逻辑一复杂就很容易出错,而且代码可读性很差。我自己的体会是,把数组API按“操作意图”梳理清楚之后,大部分数据处理场景其实都能用一行链式调用解决,调试起来也直观得多。这不是炫技,而是让代码意图变得明确的必要手段。

1.2 我习惯按“操作意图”而不是“API名字”分类

学习数组API最忌讳挨个背名字。你会发现名字相近的API行为差异极大,比如slice和splice就差一个字母,语义完全反过来;而有些行为相似的API,比如map和forEach,返回值又完全不同。所以我更推荐把API按照“我想对数组做什么”来分类,大致可以分成六组:

  • 增删改:push、pop、shift、unshift、splice,主要改变原数组
  • 查询判断:indexOf、includes、find、findIndex、some、every
  • 遍历映射:forEach、map、filter、flatMap
  • 聚合统计:reduce、reduceRight
  • 顺序调整:sort、reverse
  • 转换生成:slice、concat、join、Array.from、Array.of、fill、flat

分类之后你会发现,记忆负担小了很多,因为同一类API的返回值和使用场景往往是强相关的。比如查询判断类API要么返回数字下标,要么返回布尔值,要么返回第一个命中的元素;而遍历映射类API中,forEach返回undefined,map返回新数组,filter返回筛选后的新数组。带着这种框架去看文档,效率会高很多。

1.3 常用数组API总览表:一屏分清返回值和副作用

我把最常见的数组API列成一张总览表,包含返回值、是否改变原数组,以及一句话说明。这张表我建议你保存下来,写代码拿不准的时候扫一眼,远比翻完整篇MDN文档快。

API返回值是否改变原数组核心用途
push新数组长度是尾部追加一个或多个元素
pop被删除的元素是删除尾部一个元素
shift被删除的元素是删除头部一个元素
unshift新数组长度是头部插入一个或多个元素
splice被删除的元素数组是插入、删除、替换任意位置
slice截取出的新数组否获取子数组
concat合并后的新数组否连接多个数组
indexOf下标,找不到为-1否查询元素位置(不能识别NaN)
includes布尔值否判断是否包含某个元素(可识别NaN)
find第一个命中的元素,找不到为undefined否按条件取第一个元素
findIndex第一个命中元素的下标,找不到为-1否按条件取下标
filter筛选后的新数组否按条件筛选全部元素
map等长新数组否一对一的映射转换
forEachundefined否单纯的遍历执行
some布尔值否是否存在一个满足条件的元素
every布尔值否是否所有元素都满足条件
reduce累加后的结果否聚合统计、分组、展平
sort原数组本身是原地排序
reverse原数组本身是原地反转顺序
flat展平后的新数组否降低嵌套维度
fill原数组本身是用固定值填充
join字符串否把数组拼成字符串

这张表里最需要留意的是“是否改变原数组”这一列。很多线上问题都出在大家默认某个API会返回新数组,结果它把原数组改了,或者反过来,以为改了原数组结果没改。弄清这一列,基本能避开一半的坑。

2. 遍历与筛选:forEach、map、filter、find这么选

2.1 forEach:老老实实执行回调,但拿不到返回值

forEach是最普通的遍历API,它对数组的每个元素执行一次回调函数,回调参数依次是当前元素、当前下标、数组本身。日常最常见的用法是用来打印日志、发送埋点,或者逐个操作DOM节点。

const userList = [ { name: '用户A', age: 24 }, { name: '用户B', age: 18 }, { name: '用户C', age: 30 } ]; userList.forEach((user, index) => { console.log(`第${index + 1}位:${user.name},${user.age}岁`); });

这里要注意一个关键点:forEach的返回值永远是undefined。这意味着你没法用return把它处理后的结果带出去,也没办法直接链式调用比如forEach(...).map(...)。如果你发现自己需要把遍历结果收集成一个新数组,那大概率该用map而不是forEach。

还有一个老生常谈但很实用的提醒:forEach不支持break、continue和return提前退出。即使你在回调里写了return,也只是结束当前这一轮,循环本身不会停。早期我写前端需求时,想在遍历中找到符合条件的第一项就跳出,用forEach写了半天发现没用,最后才明白它压根没有终止机制。如果你有这个诉求,考虑用for循环、find或some来替代。

2.2 map:一对一映射,返回等长新数组

map做的事情很纯粹:把原数组的每个元素映射成一个新值,最后返回一个长度相同的新数组。它不会改动原数组。很多人容易和forEach混淆,但判断标准很简单:你到底需不需要拿到处理后的结果。

const scores = [88, 45, 72, 96]; const doubleScores = scores.map(score => score * 2); // doubleScores: [176, 90, 144, 192] // scores 原数组不变

map的回调函数必须写成有返回值的函数。最常见的低级错误是回调里用了大括号却忘了写return:arr.map(item => { item.id }),这样每个元素都返回undefined,结果就是得到一整个undefined数组。我排查过不少同事的值,发现哪里多了个空数组,基本就是这个问题。

另一个容易忽略的细节:如果数组里的元素是对象,map返回的新数组里,元素对象本身还是原数组里那个对象的引用。比如:

const users = [{ name: '用户A', age: 20 }]; const newUsers = users.map(user => { user.age += 1; return user; });

这段代码虽然返回了新数组,但newUsers和users里的对象其实是同一个引用。你改newUsers里的user.age,原数组users里的对应对象也会变。如果不想影响原数组,务必在map回调里先做浅拷贝,比如return { ...user, age: user.age + 1 }。

2.3 filter与find:一个筛选全部,一个只要第一个

filter的核心逻辑是把回调返回值为真(truthy)的元素保留下来,组成新数组返回。它非常适合做筛选,比如从订单列表里找出金额大于100的记录,从字典表里清除无效状态。

const orders = [ { id: 1, amount: 80 }, { id: 2, amount: 150 }, { id: 3, amount: 260 } ]; const bigOrders = orders.filter(order => order.amount >= 100);

filter返回的永远是一个新数组,即使没有任何元素通过筛选,返回的也是空数组,而不是null或undefined。这个特性让它很适合链式调用,例如list.filter(condition).map(...)。

find的定位和filter完全不同。它按条件查找,但只返回第一个命中的元素本身;如果没有任何元素满足条件,返回undefined。它和filter最大的区别就是返回值的“粒度”:filter给你一堆,find只给你一个。

const taskList = [ { id: 1, done: false }, { id: 2, done: true }, { id: 3, done: false } ]; const firstDoneTask = taskList.find(task => task.done); // { id: 2, done: true }

看代码的时候你会发现,find的使用场景非常明确:比如按id查某条记录、找第一个生效的配置项。如果只需要下标,还有一个findIndex,用法类似,但返回的是下标,找不到返回-1。我个人的习惯是:只要条件判断命中,而且只要一个结果,优先用find或findIndex,不要用filter再取[0],这样性能更好,语义也更清楚。

3. 聚合与判断:reduce、some、every、includes的使用细节

3.1 reduce:万能聚合函数,但必须理解初始值

reduce是数组API里最强大也最容易劝退的一个。它的回调接收四个参数:累加器、当前元素、当前下标、原数组。同时它还可以接受一个初始值。接触reduce时,我最建议大家先吃透两点:回调函数必须返回下一次累加器要用到的值;第二个参数(初始值)决定了第一轮累加器到底是什么。

最常见的求和示例长这样:

const nums = [10, 20, 30]; const total = nums.reduce((acc, cur) => acc + cur, 0); // 0 + 10 = 10 // 10 + 20 = 30 // 30 + 30 = 60 // total = 60

注意初始值0在这里很关键。如果省略初始值,reduce会把数组的第一个元素当成初始累加器,从第二个元素开始遍历。对于求和来说,结果通常一样,但对于像对象分组这种场景,省略初始值会直接抛错,因为第一轮acc是数组首元素而不是对象。

我实际用过最多的reduce场景是分组统计。比如要把一组数字按奇偶分成两组:

const data = [1, 2, 3, 4, 5, 6]; const grouped = data.reduce((acc, num) => { const key = num % 2 === 0 ? 'even' : 'odd'; acc[key] = acc[key] || []; acc[key].push(num); return acc; }, {}); // { odd: [1, 3, 5], even: [2, 4, 6] }

这里每一轮都要记得返回acc,否则下一轮拿到undefined直接报错。可以看到,reduce本质上就是把“循环+临时变量”的过程抽象成纯函数式的累积。只要你能把每次迭代要维护的状态整理清楚,它几乎能做任何聚合操作。

最后提一下reduce的空数组风险。如果数组为空且没有提供初始值,reduce会抛TypeError: Reduce of empty array with no initial value。所以不管数组是否可能为空,都强烈建议提供初始值。这个坑我踩过一次之后,就再也没有省略过第二个参数。

3.2 some与every:别小看空数组的返回值

some用来判断数组里是否存在至少一个满足条件的元素,every则要求所有元素都满足条件。它们都返回布尔值,都支持短路的逻辑,即一旦确定结果就会停止遍历。

const nums = [2, 4, 6, 8]; const hasOdd = nums.some(num => num % 2 !== 0); // false const allEven = nums.every(num => num % 2 === 0); // true

这两个API的语义清楚,问题不大,但有一个边界情况很容易写错:空数组。空数组调用some永远返回false,因为不存在任何元素;空数组调用every永远返回true,因为“所有元素都满足”在数理逻辑上对空集默认成立。这个行为在代码审查时经常被质疑,但是规范就是这么定义的,你只需要记住它,别在空数组场景下过度依赖。

实际开发中,some很适合用来做权限判断或存在性校验。比如判断某组选项里是否有任意一项被选中,用some比filter().length > 0语义更清晰,性能上也更好,因为some可以在命中第一个时就停止。

3.3 includes与indexOf:判断“存在”的两个选择

includes和indexOf都能用来判断数组里是否存在某个元素。区别最明显的地方在于NaN和语义清晰度。

indexOf用严格相等比较,它无法识别NaN,因为NaN !== NaN。如果你这样写:

const arr = [1, 2, NaN]; console.log(arr.indexOf(NaN)); // -1 console.log(arr.includes(NaN)); // true

indexOf返回-1,看起来像是NaN不存在于数组中,实际上它是存在的,只是indexOf识别不了。includes则能正确返回true,因为它内部使用了更合适的等价算法。

另一个区别是返回值。indexOf返回下标,如果你真的需要知道元素位置,就选它;如果只关心“存不存在”,选includes,读起来更自然。这两个API还都支持第二个参数fromIndex,用来指定开始查找的位置。需要注意的是,fromIndex为负数时,会从数组末尾往前数偏移量再开始查找,这跟很多人的直觉不太一样,具体表现我建议你在控制台里试一次。

3.4 用reduce做更复杂的分组统计

前面提到过基本的奇偶分组,这里再展开一点。我在处理某个模拟项目的数据报表时,需要把一批交易记录按日期分组,并统计每天的金额总和。当时直接用reduce写了一行链式逻辑,非常流畅:

const records = [ { date: '2025-01-01', amount: 100 }, { date: '2025-01-01', amount: 200 }, { date: '2025-01-02', amount: 150 } ]; const summary = records.reduce((acc, record) => { const { date, amount } = record; acc[date] = acc[date] || { totalAmount: 0, count: 0 }; acc[date].totalAmount += amount; acc[date].count += 1; return acc; }, {});

这段代码把同一天的交易聚合在一起,同时统计了笔数和总额。相比用普通for循环加三个临时变量,reduce版本的状态更集中,后续维护也好理解。但要强调的是,reduce最大的陷阱在于回调函数一旦忘记return,结果就会立刻变形。我建议在编写时先把初始值写出来,然后每一步都在脑子里过一遍:下一次进入回调时,acc到底是什么。

4. 增删改与顺序调整:push、pop、splice、slice、sort的取舍

4.1 为什么尽量不要用shift和unshift

从数组末尾增删元素,用的是push和pop,从数组头部增删元素,用的是unshift和shift。这两个操作虽然也能用,但底层实现差异很大。数组在内存中是一段连续的存储空间,push只是在末尾追加一个元素,非常轻量;unshift则要把所有已有元素整体向后挪一位,也就是所有索引都要重新计算。这个操作的时间复杂度是O(n),数据量一上来,性能差异就会非常明显。

举个我自己做过的性能测试例子:往一个有十万个元素的数组头部循环插入元素,用unshift至少比用push慢一个数量级,而且过程中还可能触发多次内存扩容。如果你的业务需要频繁在头部插入、在尾部删除,那么原生数组并不是特别合适的结构。简单场景下可以先用push后reverse,或者干脆维护两个数组;更极端的场景可以考虑链表结构。总之,shfit和unshift不是不能用,而是要有意识地在数据量较大的时候回避。

4.2 splice与slice:改动原数组和纯截取的边界

splice和slice这对名字,可能是JavaScript里最容易被搞混的组合。先说splice,它用于插入、删除或替换数组元素,会直接修改原数组,返回值是被删除的元素组成的新数组。它的参数是:(起始位置, 删除个数, 要插入的元素...)。

const arr = ['a', 'b', 'c', 'd', 'e']; const removed = arr.splice(1, 2); // removed: ['b', 'c'] // arr: ['a', 'd', 'e']

如果只是想替换,可以这样写:

const arr = ['a', 'b', 'c']; arr.splice(1, 1, 'x', 'y'); // arr: ['a', 'x', 'y', 'c']

slice则是纯读取操作,它返回从start到end(不含end)之间的元素组成的新数组,原数组完全不变。它的参数和splice不同:slice只有两个参数,start和end,end省略时表示到数组末尾。它还支持负数参数,-1表示最后一个元素。

const arr = ['a', 'b', 'c', 'd', 'e']; const sub = arr.slice(1, 3); // sub: ['b', 'c'] // arr: ['a', 'b', 'c', 'd', 'e']

我的建议是,在代码里看到slice就当作“复制一部分”,看到splice就当作“动原数组”,这俩永远别混用。既然slice不会改变原数组,它很适合用来做数组浅拷贝:const copy = arr.slice(),效果和[...arr]类似。

4.3 sort排序:不传比较器等于白排

sort可能是实际开发中“惊喜”最多的API,原因是它默认不按数字大小排序,而是把每个元素转成字符串,然后按Unicode编码顺序排序。于是[30, 1, 8, 21, 45].sort()的结果很反直觉:

const nums = [30, 1, 8, 21, 45]; nums.sort(); // [1, 21, 30, 45, 8]

看到这个结果千万别慌,这是默认行为。要按数字从小到大排,必须传入比较器函数:

nums.sort((a, b) => a - b); // [1, 8, 21, 30, 45]

比较器的规则是:如果返回值小于0,a排在b前面;返回值大于0,a排在b后面;返回0,位置保持不变。理解了这个规则,复杂排序也很好写。比如对对象数组按某个字段从大到小排,就是arr.sort((a, b) => b.age - a.age);如果还想加第二个排序条件,可以再加一次比较逻辑。

还要记住,sort是原地排序,它直接修改原数组,同时返回修改后的原数组引用。如果你不想改变原数组,请先复制一份:const sorted = [...arr].sort(...)。另一个值得了解的点是,现代V8引擎里的sort是稳定排序,也就是说如果两个元素比较结果相等,它们原来的相对顺序不会乱。这在做多字段排序时很有用,你可以先按次要字段排,再按主要字段排,顺序就能保持住。

4.4 reverse与join:做简单顺序反转和拼接

reverse用来反转数组元素的顺序,同样是在原数组上操作,返回值也是原数组本身。它的行为很直观:第一个变最后一个,最后一个变第一个。需要注意的就是它也是原地修改,要保留原数组就先拷贝一份。

const arr = [1, 2, 3, 4]; arr.reverse(); // arr: [4, 3, 2, 1]

join则是把数组的所有元素拼接成一个字符串,默认用英文逗号分隔,也可以传入自定义分隔符。它不改动原数组,非常安全。配合split可以完成字符串和数组之间的互转,这是前端处理CSV、URL参数时经常用到的组合技:

const tags = ['前端', 'JavaScript', '数组']; const tagStr = tags.join(' | '); // '前端 | JavaScript | 数组'

5. 进阶API与转换技巧:flat、flatMap、Array.from、fill

5.1 flat与flatMap:一键展平嵌套数组

flat用于把嵌套数组展平,默认只展开一层,传入Infinity可以完全展平。实际业务中,接口返回的数据偶尔会出现多层嵌套结构,比如分类树、评论回复楼层,展平后再做统计就方便很多。

const nested = [1, [2, 3], [4, [5, 6]]]; console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]] console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6] console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5, 6]

flatMap则是在flat之前先执行一次map,但只展平一层。它的语义可以理解成“先映射,再拍平一层”。常见场景是处理那种一个元素会对应生成多个结果的映射,比如把一句话拆成单词再收集起来:

const sentences = ['Hello world', 'JS arrays']; const words = sentences.flatMap(sentence => sentence.split(' ')); // ['Hello', 'world', 'JS', 'arrays']

flatMap和map加flat的区别在于,flatMap只会展平一层,而且它比分开调用更省事,也不会产生不必要的中间数组。

5.2 Array.from与Array.of:生成数组的两种冷门手段

Array.from能把类数组对象或可迭代对象转换成真数组。类数组对象最常见的就是arguments,还有DOM查询结果NodeList。在没有Array.from之前,大家得写Array.prototype.slice.call(arguments)来转换,有了Array.from之后直接一行搞定。

function collectArgs() { return Array.from(arguments).map(num => num * 2); } collectArgs(1, 2, 3); // [2, 4, 6]

Array.from的第二个参数是一个映射函数,它会在转换的同时对每个元素做处理。这可以用来快速生成一段连续的数字数组:

const range = Array.from({ length: 5 }, (_, index) => index * 2); // [0, 2, 4, 6, 8]

Array.of则是用来避免new Array(number)的歧义的。new Array(3)会创建一个长度为3的空数组,而new Array(3, 4)才会创建包含3和4的数组。Array.of不管传几个参数,都返回由这些参数组成的新数组,行为保持一致:

const a = Array.of(3); // [3] const b = Array.of(3, 4); // [3, 4]

5.3 fill与keys/entries:快速占位与索引遍历

fill用固定值填充数组的指定区间,它会修改原数组,也返回原数组。最常用的场景是初始化一个定长数组:new Array(5).fill(0),在做动态规划的表格初始化,或者渲染占位数据时都比较实用。

const arr = new Array(4).fill(false); // [false, false, false, false]

但fill也有一个隐蔽的坑:如果填充的值是对象,那么所有位置都会引用同一个对象。比如:

const items = new Array(3).fill({ name: '暂无' }); items[0].name = '已填写'; console.log(items[1].name); // '已填写' console.log(items[2].name); // '已填写'

原因就是fill不会帮你深拷贝,它只是把同一个对象引用填进了每个位置。需要每个位置都独立的话,应该用Array.from加映射函数:Array.from({ length: 3 }, () => ({ name: '暂无' }))。

entries、keys、values则是数组的迭代器方法。entries返回一个包含[key, value]的迭代器,最典型的用法是配合for...of遍历所有索引和值:

const letters = ['a', 'b', 'c']; for (const [index, letter] of letters.entries()) { console.log(index, letter); }

这三个API虽然日常用得不算频繁,但当你需要对数组做逐项索引遍历,或者需要把数组转成Map时,它们会非常趁手。

6. 常见问题与排查技巧实录

6.1 forEach和map为什么会出现“没生效”和“都是undefined”

排查经验里,这两个问题加起来能占数组相关bug的四成。先说“forEach没生效”。很多人以为在forEach回调里给数组元素重新赋值,就能改变原数组里的元素:

const nums = [1, 2, 3]; nums.forEach(num => { num = num * 2; // 没用,num只是当前元素的副本 }); console.log(nums); // [1, 2, 3]

基本类型的值在回调里修改,完全不会影响原数组。如果你真想改,要么用map生成新数组后赋值回去,要么直接用下标改:

nums.forEach((num, index) => { nums[index] = num * 2; }); console.log(nums); // [2, 4, 6]

再说“map出来全是undefined”。我在前面提到过,这通常是因为map回调里用了大括号但漏写了return。JS里x => x * 2是隐式返回,但x => { x * 2 }没有任何返回值。排查时先看回调是否用了一对大括号,再看里面有没有return,这能解决绝大多数问题。

6.2 数组去重到底有几种写法

去重是我在面试和实际开发里都经常遇到的问题。最简单的是Set去重:

const arr = [1, 2, 2, 3, 4, 4]; const unique = [...new Set(arr)]; // [1, 2, 3, 4]

Set去重用的是SameValueZero比较算法,它能识别NaN,所以[1, NaN, NaN]去重后是[1, NaN]。但要注意,Set去重对对象是引用比较,两个内容完全相同但引用不同的对象不会被去重。

如果需要按某种字段去重,我会用reduce或Map手动实现:

const items = [ { id: 1, name: 'A' }, { id: 2, name: 'B' }, { id: 1, name: 'A-2' } ]; const uniqueById = Array.from( new Map(items.map(item => [item.id, item])).values() ); // 保留后出现的 { id: 1, name: 'A-2' }

这个写法的核心是利用Map的key唯一性,把id作为key,整个对象作为value,然后用Array.from取values。思路很简单,但很有用。

6.3 稀疏数组:forEach、map、Array.from的行为差异

JS数组里可以有“空位”,比如const arr = [1, , 3],中间那个位置就是空位。空位和undefined是不同的概念,这导致各个API对空位的处理方式完全不同:

const sparse = [1, , 3]; sparse.forEach(item => console.log(item)); // 只输出 1 和 3,空位被跳过 const mapped = sparse.map(item => item * 2); // [2, , 6],空位仍然保留 const from = Array.from(sparse); // [1, undefined, 3],空位被转成 undefined

这个差异最坑的地方在于,你可能会觉得map结果里有个undefined元素是bug,其实那是空位的保留行为。如果希望空位变成正常的undefined值,用Array.from或者扩展运算符处理一下即可。这个问题在线上的表现通常是无缘无故的“undefined值”,但从数据源头去排查时却很有可能找不到,因为它压根不是真实存在的元素。

6.4 大数据量时的性能经验

数组API用起来方便,但性能上还是有一些规律。简单说,读取类API比如map、filter、slice的时间复杂度是O(n),只要不是嵌套调用,两三层以内基本都能接受。真正担心的是O(n)操作被放在循环里,变成O(n^2)。典型例子是循环里套shift或unshift,前面已经说过,头部操作本身是O(n),再套一层循环就直接爆炸。

另一个经验是,不要过度使用reduce去替代所有循环。reduce效率并不差,但可读性在某些场景下确实不如普通的for循环。如果是几万条数据,reduce完全没问题;如果是几十万条需要精细控制性能的数据,普通for循环通常比forEach和map更稳,因为少了一层回调函数调用开销。不过话说回来,绝大多数业务场景数据量都远没到需要手动优化循环的地步,写清楚、写对,比“显得性能好”更重要。

还有一点关于链式调用:arr.filter(...).map(...).reduce(...)看着很酷,但实际上会创建多个中间数组。数据量小时无所谓,数据量大时,如果性能敏感,可以适当合并成一次循环。判断标准很简单——先测,再优化,不要凭感觉改代码结构。

最后的私人建议

数组API这块内容,单纯背熟列表并不难,真正的价值在于踩过坑之后的总结。我个人的习惯是,把上面那张总览表放在一个随手能打开的备忘文档里,写代码前先判断题的类型:我是要改原数组还是生成新数组?是整个遍历还是只取一个?是判断存在还是需要下标?把这几个问题想清楚,选择自然就冒出来了。这个内容如果后续要扩展,我建议继续往迭代器、解构、性能优化方向深入,因为数组操作真正复杂的地方还在后面。希望这篇梳理对你上手JavaScript数组能有一点实际的帮助。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询