JavaScript 迭代方法
迭代指的是对一个集合(例如数组、对象、字符串、或其他可迭代对象)中的每个元素逐个进行访问和处理的过程。
迭代可以通过多种方式实现,常见的迭代方式包括使用 for 循环、for...of 循环、for...in 循环,以及数组的内置方法如 forEach、map、filter 等等。
其使用相对简单,本文将对有特别用法的几个方法进行总结。
一、 forEach
arr.forEach(function(currentValue, index, arr), thisArg)
function(currentValue, index, arr)
是一个回调函数(callback)。thisArg
(可选) 指定this
对象的上下文。
1.1 基础用法
1.1.1 遍历范围
- 遍历的范围在第一次调用
callback
前就会确定。调用forEach
后添加到数组中的项不会被callback
访问到。
var arr = [1, 2, 3, 4, 5]
arr.forEach(function (currentValue, index, arr) {
console.log(currentValue) // 输出 1, 2, 3, 4, 5
currentValue === 1 && arr.push(6)
})
console.log(arr) // output: [1, 2, 3, 4, 5, 6] (原数组已被更改,但第 6 项不会被遍历到)
2
3
4
5
6
forEach
不会直接改变调用它的对象,但是那个对象可能会被callback
函数改变。
var arr = [1, 2, 3, 4, 5]
arr.forEach(function (currentValue, index, array) {
if (index === 2) {
array[index] = 10 // 修改当前元素
array[index + 1] = 20 // 修改下一个元素
}
console.log(currentValue)
})
// 依次输出1, 2, 3, 20, 5
2
3
4
5
6
7
8
9
1.1.2 循环退出
forEach
循环中 return、return true、return false
只能跳出本次循环,不能跳出整个循环
[1, 2, 3, 4, 5].forEach((item) => {
if (item === 2) return
console.log(item)
})
// output 1,3,4,5
2
3
4
5
1.1.3 稀疏数组处理
在 forEach
方法中,稀疏数组(即存在未初始化或空槽的数组)中的未初始化元素不会被 callback
函数访问或处理。
var arr = [1, , 3, , 5]
arr.forEach(function (value, index) {
console.log('Index:', index, 'Value:', value)
})
// Index: 0 Value: 1
// Index: 2 Value: 3
// Index: 4 Value: 5
2
3
4
5
6
7
8
1.1.4 thisArg
function Logger() {
this.prefix = 'Item: ';
}
Logger.prototype.log = function(element) {
console.log(this.prefix + element);
};
const logger = new Logger();
const arr = ['a', 'b', 'c'];
arr.forEach(function(element) {
this.log(element);
}, logger);
}
// output: Item: a, Item: b, Item: c
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
因为 thisArg
参数作为(this)
传给了 forEach()
,每次调用时,它都被传给 callback
函数,作为它的 this
值。
在上面的例子中,由于 thisArg
被设置为 logger
实例,this.log(element)
调用的 log
方法的 this
是 logger
实例,因此会输出 'Item: a', 'Item: b', 'Item: c'
。
1.2 拓展使用
1.2.1 如果数组在迭代时被修改了,则其他元素会被跳过。
forEach()
不会在迭代之前创建数组的副本。
下面的例子会输出 "one", "two", "four"
。当到达包含值 "two"
的项时,整个数组的第一个项被移除了,这导致所有剩下的项上移一个位置。因为元素 "four"
正位于在数组更前的位置,所以 "three"
会被跳过。
var words = ['one', 'two', 'three', 'four']
words.forEach(function (word) {
console.log(word)
if (word === 'two') {
words.shift()
}
})
// one
// two
// four
2
3
4
5
6
7
8
9
10
1.2.2 扁平化数组
下面的示例仅用于学习目的。如果你想使用内置方法来扁平化数组,你可以考虑使用 Array.prototype.flat().
function flatten(arr) {
const result = []
arr.forEach((i) => {
if (Array.isArray(i)) result.push(...flatten(i))
else result.push(i)
})
return result
}
// Usage
const problem = [1, 2, 3, [4, 5, [6, 7], 8, 9]]
flatten(problem) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
2
3
4
5
6
7
8
9
10
11
1.2.3 通过抛出异常的方式跳出整个循环
try {
var array = ['1', '2', '3', '4']
// 执行到第3次,结束循环
array.forEach(function (item, index) {
if (index === 2) {
throw new Error('EndIterative')
}
console.log(item)
})
} catch (e) {
if (e.message != 'EndIterative') throw e
}
// 下面的代码不影响继续执行
console.log('5')
// output: 1, 2, 5
2
3
4
5
6
7
8
9
10
11
12
13
14
15
二、 some
some()
方法测试数组中是否至少有一个元素通过了被提供的函数测试。它返回的是一个 Boolean 类型的值。
arr.some(callback(element[, index[, array]])[, thisArg])
some()
遍历的元素的范围在第一次调用callback
前就已经确定了。- 在调用
some()
后被添加到数组中的值不会被callback
访问到。 - 如果数组中存在且还未被访问到的元素被
callback
改变了,则其传递给callback
的值是some()
访问到它那一刻的值。已经被删除的元素不会被访问到。
3.1 拓展使用
3.1.1 将任意值转换为布尔类型
var TRUTHY_VALUES = [true, 'true', 1]
function getBoolean(value) {
'use strict'
if (typeof value === 'string') {
value = value.toLowerCase().trim()
}
return TRUTHY_VALUES.some(function (t) {
return t === value
})
}
getBoolean(false) // false
getBoolean('false') // false
getBoolean(1) // true
getBoolean('true') // true
2
3
4
5
6
7
8
9
10
11
12
13
14
三、 reduce()
reduce()
方法对数组中的每个元素执行一个由您提供的 reducer 函数(升序执行),将其结果汇总为单个返回值。
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
callback
执行数组中每个值(如果没有提供 initialValue
则第一个值除外)的函数,包含四个参数:
accumulator
: 累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue
(见于下方)。currentValue
: 数组中正在处理的元素。index
: (可选) 数组中正在处理的当前元素的索引。 如果提供了initialValue
,则起始索引号为 0,否则从索引 1 起始。array
: (可选) 调用reduce()
的数组initialValue
可选作为第一次调用callback
函数时的第一个参数的值。
如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。 如果没有提供 initialValue
,reduce 会从索引 1 的地方开始执行 callback
方法,跳过第一个索引。如果提供 initialValue
,从索引** 0** 开始。
3.1 拓展使用
- 将二维数组转化为一维
var flattened = [
[0, 1],
[2, 3],
[4, 5],
].reduce((acc, cur) => acc.concat(cur), [])
2
3
4
5
- 按属性对 object 分类
var people = [
{ name: 'Alice', age: 21 },
{ name: 'Max', age: 20 },
{ name: 'Jane', age: 20 },
]
function groupBy(objectArray, property) {
return objectArray.reduce(function (acc, obj) {
var key = obj[property]
if (!acc[key]) {
acc[key] = []
}
acc[key].push(obj)
return acc
}, {})
}
var groupedPeople = groupBy(people, 'age')
// groupedPeople is:
// {
// 20: [
// { name: 'Max', age: 20 },
// { name: 'Jane', age: 20 }
// ],
// 21: [{ name: 'Alice', age: 21 }]
// }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- 数组去重
let arr = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4]
let result = arr.sort().reduce((init, current) => {
if (init.length === 0 || init[init.length - 1] !== current) {
init.push(current)
}
return init
}, [])
console.log(result) //[1,2,3,4,5]
2
3
4
5
6
7
8
- 按顺序运行 Promise
function runPromiseInSequence(arr, input) {
return arr.reduce(
(promiseChain, currentFunction) => promiseChain.then(currentFunction),
Promise.resolve(input),
)
}
// promise function 1
function p1(a) {
return new Promise((resolve, reject) => {
resolve(a * 5)
})
}
// promise function 2
function p2(a) {
return new Promise((resolve, reject) => {
resolve(a * 2)
})
}
// function 3 - will be wrapped in a resolved promise by .then()
function f3(a) {
return a * 3
}
// promise function 4
function p4(a) {
return new Promise((resolve, reject) => {
resolve(a * 4)
})
}
const promiseArr = [p1, p2, f3, p4]
runPromiseInSequence(promiseArr, 10).then(console.log) // 1200
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
执行流程
- runPromiseInSequence 函数通过 reduce 方法将传入的函数数组(promiseArr)按顺序串联起来。
- 初始化时用 Promise.resolve(input) 作为第一个 Promise,然后依次执行每个函数(p1、p2、f3 和 p4),每个函数的返回值会作为下一个函数的输入。
- 如果函数是异步的,它返回 Promise,如果是同步的(如 f3),它的返回值会自动包装成一个已解析的 Promise。
- 最终,所有函数执行完成后,返回的 Promise 的最终结果(1200)会被输出到控制台。
四、 reduceRight()
reduceRight()
方法和 reduce()
方法类似,但它是从右到左遍历数组,并且它是从右到左调用 callback
函数。
arr.reduceRight(callback(accumulator, currentValue[, index[, array]])[, initialValue])
4.1 拓展使用
- 运行一个带有回调每个函数将其结果传给下一个的异步函数列表
const waterfall =
(...functions) =>
(callback, ...args) =>
functions.reduceRight(
(composition, fn) =>
(...results) =>
fn(composition, ...results),
callback,
)(...args)
const randInt = (max) => Math.floor(Math.random() * max)
const add5 = (callback, x) => {
setTimeout(callback, randInt(1000), x + 5)
}
const mult3 = (callback, x) => {
setTimeout(callback, randInt(1000), x * 3)
}
const sub2 = (callback, x) => {
setTimeout(callback, randInt(1000), x - 2)
}
const split = (callback, x) => {
setTimeout(callback, randInt(1000), x, x)
}
const add = (callback, x, y) => {
setTimeout(callback, randInt(1000), x + y)
}
const div4 = (callback, x) => {
setTimeout(callback, randInt(1000), x / 4)
}
const computation = waterfall(add5, mult3, sub2, split, add, div4)
computation(console.log, 5) // -> 14
// same as:
const computation2 = (input, callback) => {
const f6 = (x) => div4(callback, x)
const f5 = (x, y) => add(f6, x, y)
const f4 = (x) => split(f5, x)
const f3 = (x) => sub2(f4, x)
const f2 = (x) => mult3(f3, x)
add5(f2, input)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- 定义可组合函数
组合函数的概念很简单,它只是简单地结合了多个函数。它是一个从右向左流动的函数,用上一个函数的输出调用每个函数。
const compose =
(...args) =>
(value) =>
args.reduceRight((acc, fn) => fn(acc), value)
// Increament passed number
const inc = (n) => n + 1
// Doubles the passed value
const double = (n) => n * 2
// using composition function
console.log(compose(double, inc)(2)) // 6
// using composition function
console.log(compose(inc, double)(2)) // 5
2
3
4
5
6
7
8
9
10
11
12
五、 for...in
for...in
语句以任意顺序遍历一个对象的除 Symbol
以外的 可枚举 属性。
如果你只要考虑对象本身的属性,而不是它的原型,那么使用 getOwnPropertyNames()
或执行 hasOwnProperty()
来确定某属性是否是对象本身的属性。
// hasOwnProperty
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key)
}
}
// getOwnPropertyNames
var obj = { a: 1, b: 2, c: 3 }
var keys = Object.getOwnPropertyNames(obj)
console.log(keys) // ['a', 'b', 'c']
2
3
4
5
6
7
8
9
10
六、 for...of
在 可迭代对象 上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
let iterable = [10, 20, 30]
for (let value of iterable) {
value += 1
console.log(value)
}
// 11
// 21
// 31
2
3
4
5
6
7
8
6.1 与 for in
比较
无论是 for...in
还是 for...of
语句都是迭代一些东西。它们之间的主要区别在于它们的迭代方式。 for...in
语句以任意顺序迭代对象的 可枚举属性。
for...of
语句遍历 可迭代对象 定义要迭代的数据。
以下示例显示了与 Array 一起使用时,for...of
循环和 for...in
循环之间的区别。
Object.prototype.objCustom = function () {}
Array.prototype.arrCustom = function () {}
let iterable = [3, 5, 7]
iterable.foo = 'hello'
for (let i in iterable) {
console.log(i) // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}
for (let i in iterable) {
if (iterable.hasOwnProperty(i)) {
console.log(i) // logs 0, 1, 2, "foo"
}
}
for (let i of iterable) {
console.log(i) // logs 3, 5, 7
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15