JavaScript 数组如何遍历? 数组遍历到底有多少种方式? 下面本篇文章就来给大家介绍一下 JavaScript 数组遍历的几种方法. 有一定的参考价值, 有需要的朋友可以参考一下, 希望对大家有所帮助.
对于"数组遍历"这个问题, 其实答案很宽泛, 关键在于你能不能列举出一定数量的方法以及描述它们之间的区别. 本文即介绍一下数组的基本遍历操作和高阶函数.
一, 数组基本遍历
本部分介绍4种最常用的遍历方式.
1.for...in
for...in 其实是对象的遍历方式, 并不是数组专有, 使用 for...in 将循环遍历对象本身的所有可枚举属性, 以及对象从其构造函数原型中继承的属性, 其遍历顺序与 Object.keys()函数取到的列表一致.
该方法会遍历数组中非数字下标的元素, 会忽略空元素:
- let list = [7, 5, 2, 3]
- list[10] = 1
- list['a'] = 1
- console.log(JSON.stringify(Object.keys(list)))
- for (let key in list) {
- console.log(key, list[key])
- }
输出:
- > ["0","1","2","3","10","a"]
- > 0, 7
- > 1, 5
- > 2, 2
- > 3, 3
- > 10, 1
- > a, 1
这个方法遍历数组是最坑的, 它通常表现为有序, 但是因为它是按照对象的枚举顺序来遍历的, 也就是规范没有规定顺序的, 所以具体实现是由着浏览器来的. MDN 文档里也明确建议 "不要依赖其遍历顺序":
2.for...of
这个方法用于可迭代对象的迭代, 用来遍历数组是有序的, 并且迭代的是数组的值. 该方法不会遍历非数字下标的元素, 同时不会忽略数组的空元素:
- let list = [7, 5, 2, 3]
- list[5] = 4
- list[4] = 5
- list[10] = 1
- // 此时下标 6,7,8,9 为空元素
- list['a'] = 'a'
- for (let value of list) {
- console.log(value)
- }
输出:
- > 7
- > 5
- > 2
- > 3
- > 5
- > 4
- > // 遍历空元素
- > // 遍历空元素
- > // 遍历空元素
- > // 遍历空元素
- > 1
3. 取数组长度进行遍历
该方法和方法 2 比较像, 是有序的, 不会忽略空元素.
- let list = ['a', 'b', 'c', 'd']
- list[4] = 'e'
- list[10] = 'z'
- list['a'] = 0
- for (let idx = 0; idx <list.length; idx++) {
- console.log(idx, list[idx])
- }
输出:
- > 0, a
- > 1, b
- > 2, c
- > 3, d
- > 4, e
- > 5, // 空元素
- > 6,
- > 7,
- > 8,
- > 9,
- > 10, z
4.forEach 遍历
forEach 是数组的一个高阶函数, 用法如下:
arr.forEach(callback[, thisArg])
参数说明:
callback
为数组中每个元素执行的函数, 该函数接收三个参数:
currentValue
数组中正在处理的当前元素.
index 可选
数组中正在处理的当前元素的索引.
array 可选
forEach() 方法正在操作的数组.
thisArg 可选
可选参数. 当执行回调函数时用作 this 的值(参考对象).
forEach 遍历数组会按照数组下标升序遍历, 并且会忽略空元素:
- let list = ['a', 'b', 'c', 'd']
- list[4] = 'e'
- list[10] = 'z'
- list['a'] = 0
- list.forEach((value, key, list) => {
- console.log(key, value)
- })
输出:
- > 0, a
- > 1, b
- > 2, c
- > 3, d
- > 4, e
- > 10, z
有一个很容易忽略的细节, 我们都应该尽可能地避免在遍历中取增删数组的元素, 否则会出现一些意外的情况, 并且不同的遍历方法还会有不同的表现.
for...of 和 forEach 遍历中删除元素
比如 for...of 遍历中删除元素:
- let list = ['a', 'b', 'c', 'd']
- for (let item of list) {
- if (item === 'a') {
- list.splice(0, 1)
- }
- console.log(item)
- }
输出:
> a> c> d
forEach 遍历中删除元素:
- let list = ['a', 'b', 'c', 'd']
- list.forEach((item, idx) => {
- if (item === 'a') {
- list.splice(0, 1)
- }
- console.log(item)
- })
输出:
> a> c> d
可以看到, 二者表现一致, 遍历到a的时候, 把a删除, 则b会被跳过, 增加元素则略为不同.
for...of 和 forEach 遍历中增加元素
for...of 遍历中增加元素:
- let list = ['a', 'b', 'c', 'd']
- for (let item of list) {
- if (item === 'a') {
- list.splice(1, 0, 'e')
- }
- console.log(item)
- }
输出:
> a> e> b> c> d
forEach 遍历中增加元素:
- let list = ['a', 'b', 'c', 'd']
- list.forEach((item, idx) => {
- if (item === 'a') {
- list.splice(1, 0, 'e')
- }
- console.log(item)
- })
输出:
> a> e> b> c
咦, 少了个'd'! 可以看到, 其实 forEach 遍历次数在一开始就已确定, 所以最后的'd'没有输出出来, 这是 forEach 和 for 遍历数组的一个区别, 另一个重要区别是 forEach 不可用 break, continue, return 等中断循环, 而 for 则可以.
总之, 在遍历数组过程中, 对数组的操作要非常小心, 这一点 python,JS 很相似, 因为两门语言中, 对象 / 字典和数组都是引用, 都为可变对象.
二, 利用高阶函数遍历数组
上面介绍的4种算是比较标准的遍历方式, 不过 JS 中数组还有很多的高阶函数, 这些函数其实都可以达到遍历数组的目的, 只不过每个函数的应用场景不同, 下面简单介绍一下.
1. map
map() 方法参数与 forEach 完全相同, 二者区别仅仅在于 map 会将回调函数的返回值收集起来产生一个新数组.
比如将数组中每个元素的 2 倍输出为一个新数组:
- let list = [1, 2, 3, 4]
- let result = list.map((value, idx) => value * 2)
- console.log(result) // 输出[2,4,6,8]
- 2.filter
filter() 参数与 forEach 完全一致, 不过它的 callback 函数应该返回一个真值或假值. filter() 方法创建一个新数组, 新数组包含所有使得 callback 返回值为真值 (Truthy, 与 true 有区别) 的元素.
比如过滤数组中的偶数:
- let list = [1, 2, 3, 4]
- let result = list.filter((value, idx) => value % 2 === 0)
- console.log(result) // 输出[2,4]
- 3. find/findIndex
find() 方法返回数组中使 callback 返回值为 Truthy 的第一个元素的值, 没有则返回 undefined. 使用非常简单, 比如找出数组中第一个偶数:
- let list = ['1', '2', '3', '4']
- let result = list.find(value => value % 2 === 0)
- console.log(result) // 输出 2
findIndex()方法与 find 方法很类似, 只不过 findIndex 返回使 callback 返回值为 Truthy 的第一个元素的索引, 没有符合元素则返回 - 1. 比如找出数组中第一个偶数的下标:
- let list = [1, 2, 3, 4]
- let result = list.findIndex(value => value % 2 === 0)
- console.log(result) // 输出 1
- 4.every/some
两个函数接收参数都与以上函数相同, 返回都是布尔值. every 用于判断是否数组中每一项都使得 callback 返回值为 Truthy,some 用于判断是否至少存在一项使得 callback 元素返回值为 Truthy.
- let list = [1, 2, 3, 4]
- // 判断数组中是否每个元素小于 10
- let result = list.every(value => {
- return value <10
- })
- console.log(result) // 输出 true
- // 判断是否每个元素大于 2
- result = list.every(value => {
- return value> 2
- })
- console.log(result) // 输出 false
- // 判断是数组中否存在 1
- result = list.some(value => {
- return value === 1
- })
- console.log(result) // 输出 true
- // 判断数组中是否存在大于 10 的数
- result = list.some(value => {
- return value> 10
- })
- console.log(result) // 输出 false
5.reduce/reduceRight 累加器
参数与其它函数有所不同:
callback
执行数组中每个值的函数, 包含四个参数:
accumulator
累计器累计回调的返回值; 它是上一次调用回调时返回的累积值, 或 initialValue(见于下方).
currentValue
数组中正在处理的元素.
currentIndex 可选
数组中正在处理的当前元素的索引. 如果提供了 initialValue, 则起始索引号为 0, 否则为 1.
array 可选
调用 reduce()的数组
initialValue 可选
作为第一次调用 callback 函数时的第一个参数的值. 如果没有提供初始值, 则将使用数组中的第一个元素. 在没有初始值的空数组上调用 reduce 将报错.
reduce() 方法对数组中的每个元素执行一个由您提供的 reducer 函数(升序执行), 将其结果汇总为单个返回值, 而 reduceRight 只是遍历顺序相反而已.
比如很常见的一个需求是, 把一个如下结构的 list 变成一个树形结构, 使用 forEach 和 reduce 可以轻松实现.
列表结构:
- let list = [
- {
- id: 1,
- parentId: ''
- },
- {
- id: 2,
- parentId: ''
- },
- {
- id: 3,
- parentId: 1
- },
- {
- id: 4,
- parentId: 2,
- },
- {
- id: 5,
- parentId: 3
- },
- {
- id: 6,
- parentId: 3
- }
- ]
树形结构:
- [
- {
- "id":1,
- "parentId":"",
- "children":[
- {
- "id":3,
- "parentId":1,
- "children":[
- {
- "id":5,
- "parentId":3
- },
- {
- "id":6,
- "parentId":3
- }
- ]
- }
- ]
- },
- {
- "id":2,
- "parentId":"",
- "children":[
- {
- "id":4,
- "parentId":2
- }
- ]
- }
- ]
利用 reduce 和 forEach 实现 list 转为树形结构:
- function listToTree(srcList) {
- let result = []
- // reduce 收集所有节点信息存放在对象中, 可以用 forEach 改写, 不过代码会多几行
- let nodeInfo = list.reduce((data, node) => (data[node.id] = node, data), {})
- // forEach 给所有元素找妈妈
- srcList.forEach(node => {
- if (!node.parentId) {
- result.push(node)
- return
- }
- let parent = nodeInfo[node.parentId]
- parent.children = parent.children || []
- parent.children.push(node)
- })
- return result
- }
以上即为本文围绕数组遍历介绍的数组基本操作. 这些高阶函数其实都可以用于数组遍历(如果想强行遍历的话, 比如 some 的 callback 恒返回 false), 不过实际使用中应该根据不同的需求选用不同的方法.
至此, 面试中遇到 "数组遍历有多少种方法?" 这种问题, 你可以回答 "10 种以上" 了, 毕竟, 本文介绍了 12 种...
最后, JS 其实是一门特别愚蠢的语言, 有时候你交给它的事情, 它不会办不说, 竟然还会骂人! 不信? 控制台输入下面的算式试试:
(![]+{})[-~!+[]^-~[]]+([]+{})[-~!![]]
Just for fun. 别太认真~,~
本文转载自: https://segmentfault.com/a/1190000020233039
来源: http://www.css88.com/web/javascript/16104.html