迭代
迭代指的是对一个集合(例如数组、对象、字符串、或其他可迭代对象)中的每个元素逐个进行访问和处理的过程。
迭代可以通过多种方式实现,常见的迭代方式包括使用 for 循环、for...of 循环、for...in 循环,以及数组的内置方法如 forEach、map、filter 等等。 本文将总结常见的迭代方法及其实现。
1. for 循环
1.1 基础用法
// 格式:
for ([initialization]; [condition]; [final - expression]) statement
initialization
表示一个表达式 (包含赋值语句) 或者变量声明。被用于初始化一个计数器。condition
表示一个表达式,在每次循环迭代之前被求值。如果该表达式的值为 true,则执行循环体,否则跳过循环体。final-expression
表示一个表达式,在每次循环迭代之后被求值。该表达式的值不会被使用。statement
表示一个或多个语句,在每次循环迭代时被执行。
// 示例
for (var i = 0; i < 5; i++) {
console.log(i)
}
1.2 循环退出
循环退出有三种方式:
break
语句: 立即退出循环,不再执行循环体后面的语句。continue
语句: 立即结束本次循环迭代,开始下一次循环迭代。return
语句: 立即退出循环并返回一个值。
continue
用于退出本次循环,break
用于退出整个循环,这两个语句可以指定 label
从而可以退出特定的循环。
var num = 0
outermost: for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
break outermost
}
num++
}
}
alert(num) //55
var num = 0
outermost: for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
continue outermost
}
num++
}
}
alert(num) //45
return
直接跳出整个循环。 与 return
不同的是,在多层循环中,break
不是跳出函数,而是跳出最里层的 for
循环,外面的循环和最外层 for
循环后面的语句也将继续执行。
function count() {
for (var i = 0; i < 5; i++) {
for (var j = 0; j < 5; j++) {
if (i == 2 && j == 2) {
return i * j
}
}
}
}
console.log(count()) // 4
function count() {
for (var i = 0; i < 5; i++) {
for (var j = 0; j < 5; j++) {
if (i == 2 && j == 2) {
break
}
}
}
return i * j
}
console.log(count()) // 25
2. forEach
arr.forEach(function(currentValue, index, arr), thisArg)
function(currentValue, index, arr)
是一个回调函数,其中 currentValue
是数组中正在处理的当前元素,index
是当前元素的索引,arr
是数组本身。
thisArg
(可选) 指定this
对象的上下文。
2.1 基础用法
2.1.1 遍历范围
- 遍历的范围在第一次调用
callback
前就会确定。调用forEach
后添加到数组中的项不会被callback
访问到。 forEach
不会直接改变调用它的对象,但是那个对象可能会被callback
函数改变。
var arr = [1, 2, 3, 4, 5]
arr.forEach(function (currentValue, index, arr) {
console.log(currentValue)
})
// output: 1, 2, 3, 4, 5
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 项不会被遍历到)
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.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.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.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
因为 thisArg
参数作为(this)
传给了 forEach()
,每次调用时,它都被传给 callback
函数,作为它的 this
值。
在上面的例子中,由于 thisArg
被设置为 logger
实例,this.log(element)
调用的 log
方法的 this
是 logger
实例,因此会输出 'Item: a', 'Item: b', 'Item: c'
。
注意
箭头函数不会绑定自己的 this 值,而是从外部上下文继承 this。如果使用箭头函数,thisArg 参数将被忽略:
const obj = {
value: 42,
log: function (element) {
console.log(this.value + element)
},
}
const arr = [1, 2, 3]
arr.forEach((element) => {
this.log(element) // 注意:`this` 不会被绑定到 `obj`
}, obj)
2.2 使用方式拓展
2.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.2.2 扁平化数组
下面的示例仅用于学习目的。如果你想使用内置方法来扁平化数组,你可以考虑使用 Array.prototype.flat().
/**
* Flattens passed array in one dimensional array
*
* @params {array} arr
* @returns {array}
*/
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.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
3. map
map()
方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// return element for new_array
}[, thisArg])
- 回调函数可以接受三个参数:当前元素的值、当前元素的索引(可选)、原数组(可选)。
- 如果没有提供 thisArg,则 this 绑定到全局对象。
- 同
forEach
一样,callback
函数只会在有值的索引上被调用。
4. every
every()
方法测试一个数组内的所有元素是否都能通过某个指定函数的测试,返回一个布尔值。
every
遍历的元素范围在第一次调用callback
之前就已确定了。在调用every
之后添加到数组中的元素不会被callback
访问到。- 如果数组中存在的元素被更改,则他们传入
callback
的值是every
访问到他们那一刻的值。 - 那些被删除的元素或从来未被赋值的元素将不会被访问到。
var a = [1, 2, 3, 4].every(function (item, i) {
return item < 3
})
// 4 > 3 所以 a 值为 false
在 every callback
函数体内,return false
跳出整个循环,而return true
则会跳出本次循环,继续下一轮循环;
5. some
some()
方法测试数组中是否至少有一个元素通过了被提供的函数测试。它返回的是一个 Boolean 类型的值。
arr.some(callback(element[, index[, array]])[, thisArg])
some()
遍历的元素的范围在第一次调用callback
前就已经确定了。- 在调用
some()
后被添加到数组中的值不会被callback
访问到。 - 如果数组中存在且还未被访问到的元素被
callback
改变了,则其传递给callback
的值是some()
访问到它那一刻的值。已经被删除的元素不会被访问到。
5.1 拓展使用
5.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
6. filter
filter()
方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素。
arr.filter(callback(element[, index[, array]])[, thisArg])
- filter 遍历的元素范围在第一次调用 callback 之前就已经确定了。
- 在调用 filter 之后被添加到数组中的元素不会被 filter 遍历到。
- 如果已经存在的元素被改变了,则他们传入 callback 的值是 filter 遍历到它们那一刻的值。被删除或从来未被赋值的元素不会被遍历到。
7. 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** 开始。
7.1 拓展使用
7.1.1 将二维数组转化为一维
var flattened = [
[0, 1],
[2, 3],
[4, 5],
].reduce((acc, cur) => acc.concat(cur), [])
7.1.2 按属性对 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 }]
// }
7.1.3 数组去重
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]
7.1.4 按顺序运行 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
7.1.5 使用 reduce 实现 map
if (!Array.prototype.mapUsingReduce) {
Array.prototype.mapUsingReduce = function (callback, thisArg) {
return this.reduce(function (mappedArray, currentValue, index, array) {
mappedArray[index] = callback.call(thisArg, currentValue, index, array)
return mappedArray
}, [])
}
}
;[1, 2, , 3].mapUsingReduce((currentValue, index, array) => currentValue + index + array.length) // [5, 7, , 10]
8. reduceRight()
reduceRight()
方法和 reduce()
方法类似,但它是从右到左遍历数组,并且它是从右到左调用 callback
函数。
arr.reduceRight(callback(accumulator, currentValue[, index[, array]])[, initialValue])
8.1 拓展使用
8.1.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)
}
8.1.2 定义可组合函数
组合函数的概念简单,它只是简单地结合了多个函数。它是一个从右向左流动的函数,用上一个函数的输出调用每个函数。
/**
* Function Composition is way in which result of one function can
* be passed to another and so on.
*
* h(x) = f(g(x))
*
* Function execution happens right to left
*
* https://en.wikipedia.org/wiki/Function_composition
*/
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
9. 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']
10. for...of
在 可迭代对象 上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
let iterable = [10, 20, 30]
for (let value of iterable) {
value += 1
console.log(value)
}
// 11
// 21
// 31
10.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
}
11. find
find()
方法返回数组中满足提供的测试函数的第一个元素的值。
arr.find(callback[, thisArg])
- 数组中第一个满足所提供测试函数的元素的值,否则返回
undefined
。
var arr = [1, 2, 3, 4, 5]
var found = arr.find(function (element, index, array) {
return element > 3
})
console.log(found) // 4
callback
函数会为数组中的每个索引调用即从0
到length - 1
,而不仅仅是那些被赋值的索引,这意味着对于稀疏数组来说,该方法的效率要低于那些只遍历有值的索引的方法。
const sparseArray = [1, , 3, , 5]
const result = sparseArray.find((element, index) => {
console.log(`Index: ${index}, Element: ${element}`)
return element === 5
})
console.log('Result:', result)
// Index: 0, Element: 1
// Index: 1, Element: undefined
// Index: 2, Element: 3
// Index: 3, Element: undefined
// Index: 4, Element: 5
// Result: 5
在这个例子中,即使数组中的第二和第四个索引没有值,callback 函数仍然会在这些索引上被调用。这可能会在稀疏数组中导致不必要的计算。
- 如果数组中一个尚未被
callback
函数访问到的元素的值被callback
函数所改变,那么当callback
函数访问到它时,它的值是将是根据它在数组中的索引所访问到的当前值;被删除的元素仍旧会被访问到,但是其值已经是undefined
了。
const array = [10, 20, 30, 40]
// 修改元素的值
const result = array.find((element, index, arr) => {
if (index === 1) {
// 修改下一个元素
arr[2] = 50
}
if (element === 50) {
return true // 找到修改后的值
}
return false
})
console.log('Result:', result) // 输出: 50
console.log('Array:', array) // 输出: [10, 20, 50, 40]
在这个例子中,当 find
方法访问索引 1 时,callback
函数将数组中索引 2 的值从 30 修改为 50。当 find
方法随后访问索引 2 时,它会看到修改后的值 50,并根据这个新值做出判断。
const array = [10, 20, 30, 40]
// 删除元素
const result = array.find((element, index, arr) => {
if (index === 1) {
// 删除下一个元素
delete arr[2]
}
console.log(`Index: ${index}, Element: ${element}`)
return element === undefined
})
console.log('Result:', result) // 输出: undefined
console.log('Array:', array) // 输出: [10, 20, undefined, 40]
在这个例子中,当 find
方法访问索引 1 时,callback
函数删除了索引 2 处的元素。随后,find
方法访问索引 2,但此时该位置的值已经变成 undefined
,因此该索引仍然会被访问到,但值为 undefined
。
12. findIndex
findIndex()
方法返回数组中满足提供的测试函数的第一个元素的索引。
arr.findIndex(callback[, thisArg])
- 数组中第一个满足所提供测试函数的元素的索引,否则返回
-1
。 callback
函数会为数组中的每个索引调用即从0
到length - 1
,而不仅仅是那些被赋值的索引,这意味着对于稀疏数组来说,该方法的效率要低于那些只遍历有值的索引的方法。- 如果数组中一个尚未被
callback
函数访问到的元素的值被callback
函数所改变,那么当callback
函数访问到它时,它的值是将是根据它在数组中的索引所访问到的当前值;被删除的元素仍旧会被访问到,但是其值已经是undefined
了。
其他用法与 find()
方法相同。
13. entries
entries()
方法返回一个新的 Array Iterator 对象,该对象包含数组每个索引的键值对。
var arr = ['a', 'b', 'c']
var iterator = arr.entries()
console.log(iterator) // Array Iterator [0, 'a'], [1, 'b'], [2, 'c']
- 数组迭代器中存储的是原数组的地址,而不是数组元素值。
var arr = ['a', 'b', 'c']
var iterator = arr.entries()
console.log(iterator.next().value) // [0, 'a']
arr[1] = 'n'
console.log(iterator.next().value) // [1, 'n']
- 如果数组中元素改变,那么迭代器的值也会改变
var arr = ['a', 'b', 'c']
var iterator = arr.entries()
console.log(iterator.next().value) // [0, 'a']
arr.push('d')
console.log(iterator.next().value) // [3, 'd']
entries()
方法也会遍历稀疏数组的空位,并返回这些空位的索引和 undefined 值。
const sparseArray = [1, , 3]
for (const [index, value] of sparseArray.entries()) {
console.log(index, value)
}
// 0 1
// 1 undefined
// 2 3
14. keys()
keys()
方法返回一个包含数组中每个索引键的 Array Iterator
对象。
var arr = ['a', , 'c']
var sparseKeys = Object.keys(arr)
var denseKeys = [...arr.keys()]
console.log(sparseKeys) // ['0', '2']
console.log(denseKeys) // [0, 1, 2]
keys()
方法也会遍历稀疏数组的空位,并返回这些空位的索引。
const sparseArray = [1, , 3]
for (const key of sparseArray.keys()) {
console.log(key)
}
// 0
// 1
// 2
keys()
方法返回的迭代器对象是不可变的,不能被修改。
var arr = ['a', 'b', 'c']
var iterator = arr.keys()
console.log(iterator.next().value) // 0
console.log(iterator.next().value) // 1
console.log(iterator.next().value) // 2
arr.push('d')
console.log(iterator.next().value) // undefined
15. values()
values()
方法返回一个包含数组中每个元素值的 Array Iterator
对象。
Array.prototype.values
是Array.prototype[Symbol.iterator]
的默认实现。
Array.prototype.values === Array.prototype[Symbol.iterator] // true
- 数组迭代器中存储的是原数组的地址,而不是数组元素值。
var arr = ['a', 'b', 'c', 'd', 'e']
var iterator = arr.values()
console.log(iterator) // Array Iterator { }
iterator.next().value // "a"
arr[1] = 'n'
iterator.next().value // "n"
- 如果数组中元素改变,那么迭代器的值也会改变
var arr = ['a', 'b', 'c', 'd', 'e']
var iterator = arr.values()
console.log(iterator.next().value) // "a"
arr.push('f')
console.log(iterator.next().value) // "f"
values()
方法也会遍历稀疏数组的空位,并返回这些空位的 undefined 值。
const sparseArray = [1, , 3]
for (const value of sparseArray.values()) {
console.log(value)
}
// 1
// undefined
// 3
17. 迭代器
迭代器是一种特殊的对象,它实现了两个方法:
next()
方法:返回一个对象,该对象包含两个属性:done 和 value。done 属性是一个布尔值,表示迭代是否完成;value 属性是迭代返回的下一个值。当 done 为 true 时,迭代结束。return()
方法:结束迭代并返回一个值。
迭代器对象可以被用来遍历某些数据结构,如数组、Map、Set、字符串、TypedArray、arguments 对象等。
const arr = [1, 2, 3, 4, 5]
const iterator = arr[Symbol.iterator]()
while (true) {
const { done, value } = iterator.next()
if (done) break
console.log(value)
}
// 1
// 2
// 3
// 4
// 5
17.1 迭代器协议
迭代器协议(Iterator protocol)是一种定义对象之间如何进行交流的协议。它规定了如何创建一个对象的迭代器,以及如何通过迭代器来访问对象的元素。
任何对象都可以实现迭代器协议,只要它定义了 next()
方法。next()
方法返回一个包含 done
和 value
属性的对象。done
属性是一个布尔值,表示迭代是否完成;value
属性是迭代返回的下一个值。当 done
为 true
时,迭代结束。
const obj = {
values: function () {
let index = 0
return {
next: function () {
if (index < this.arr.length) {
return { done: false, value: this.arr[index++] }
} else {
return { done: true, value: undefined }
}
},
arr: [1, 2, 3, 4, 5],
}
},
}
const iterator = obj.values()
while (true) {
const { done, value } = iterator.next()
if (done) break
console.log(value)
}
// 1
// 2
// 3
// 4
// 5
17.3 迭代器与生成器
迭代器与生成器是两种不同的概念。
- 迭代器是一种对象,它实现了
next()
方法,用来返回一个包含done
和value
属性的对象。 - 生成器是一种函数,它使用
yield
关键字来返回一个值,并且可以暂停执行并恢复执行。
生成器与迭代器的关系类似于函数与函数调用的关系。生成器可以被调用,返回一个迭代器对象。
function* generator() {
yield 1
yield 2
yield 3
}
const iterator = generator()
console.log(iterator.next().value) // 1
console.log(iterator.next().value) // 2
console.log(iterator.next().value) // 3
console.log(iterator.next().value) // undefined
生成器与迭代器的区别在于:
- 生成器可以暂停执行并恢复执行,而迭代器只能一次性返回所有的值。
- 生成器可以返回任意类型的值,而迭代器只能返回可迭代的值。
- 生成器可以被暂停并恢复,而迭代器只能被遍历一次。
17.4 Symbol.iterator
为了为越来越多的数据结构提供统一的迭代器协议,ECMAScript 2015 引入了 Symbol.iterator
属性。
Symbol.iterator
属性是一个方法,它返回一个新的迭代器对象,该对象包含 next()
方法。
任一数据只要实现了 Symbol.iterator
属性,就可以被 for of、扩展运算符 (...) 等语句迭代。
const range = {
start: 1,
end: 5,
[Symbol.iterator]() {
let current = this.start
const end = this.end
return {
next() {
if (current <= end) {
return { value: current++, done: false }
} else {
return { value: undefined, done: true }
}
},
}
},
}
console.log([...range]) // [1, 2, 3, 4, 5]