Skip to content

迭代

迭代指的是对一个集合(例如数组、对象、字符串、或其他可迭代对象)中的每个元素逐个进行访问和处理的过程。
迭代可以通过多种方式实现,常见的迭代方式包括使用 for 循环、for...of 循环、for...in 循环,以及数组的内置方法如 forEach、map、filter 等等。 本文将总结常见的迭代方法及其实现。 迭代方法

1. for 循环

1.1 基础用法

javascript
// 格式:
for ([initialization]; [condition]; [final - expression]) statement
  • initialization 表示一个表达式 (包含赋值语句) 或者变量声明。被用于初始化一个计数器。
  • condition 表示一个表达式,在每次循环迭代之前被求值。如果该表达式的值为 true,则执行循环体,否则跳过循环体。
  • final-expression 表示一个表达式,在每次循环迭代之后被求值。该表达式的值不会被使用。
  • statement 表示一个或多个语句,在每次循环迭代时被执行。
javascript
// 示例
for (var i = 0; i < 5; i++) {
  console.log(i)
}

1.2 循环退出

循环退出有三种方式:

  • break 语句: 立即退出循环,不再执行循环体后面的语句。
  • continue 语句: 立即结束本次循环迭代,开始下一次循环迭代。
  • return 语句: 立即退出循环并返回一个值。

continue 用于退出本次循环,break 用于退出整个循环,这两个语句可以指定 label 从而可以退出特定的循环。

javascript
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 循环后面的语句也将继续执行。

javascript
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

javascript
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  函数改变。
javascript
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 只能跳出本次循环,不能跳出整个循环

javascript
;[1, 2, 3, 4, 5].forEach((item) => {
  if (item === 2) return
  console.log(item)
})
// output 1,3,4,5

2.1.3 稀疏数组处理

forEach 方法中,稀疏数组(即存在未初始化或空槽的数组)中的未初始化元素不会被 callback 函数访问或处理。

javascript
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

javascript
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 方法的 thislogger 实例,因此会输出 'Item: a', 'Item: b', 'Item: c'

注意

箭头函数不会绑定自己的 this 值,而是从外部上下文继承 this。如果使用箭头函数,thisArg 参数将被忽略:

javascript
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" 会被跳过。

javascript
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().

javascript
/**
 * 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 通过抛出异常的方式跳出整个循环

javascript
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()方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

javascript
 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  访问到他们那一刻的值。
  • 那些被删除的元素或从来未被赋值的元素将不会被访问到。
javascript
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 类型的值。

javascript
  arr.some(callback(element[, index[, array]])[, thisArg])
  • some()  遍历的元素的范围在第一次调用  callback 前就已经确定了。
  • 在调用  some()  后被添加到数组中的值不会被  callback  访问到。
  • 如果数组中存在且还未被访问到的元素被  callback  改变了,则其传递给  callback  的值是  some()  访问到它那一刻的值。已经被删除的元素不会被访问到。

5.1 拓展使用

5.1.1 将任意值转换为布尔类型

javascript
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() 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素。

javascript
arr.filter(callback(element[, index[, array]])[, thisArg])
  • filter 遍历的元素范围在第一次调用 callback 之前就已经确定了。
  • 在调用 filter 之后被添加到数组中的元素不会被 filter 遍历到。
  • 如果已经存在的元素被改变了,则他们传入 callback 的值是 filter 遍历到它们那一刻的值。被删除或从来未被赋值的元素不会被遍历到。

7. reduce()

reduce() 方法对数组中的每个元素执行一个由您提供的 reducer 函数(升序执行),将其结果汇总为单个返回值。

javascript
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 将二维数组转化为一维

javascript
var flattened = [
  [0, 1],
  [2, 3],
  [4, 5],
].reduce((acc, cur) => acc.concat(cur), [])

7.1.2 按属性对 object 分类

javascript
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 数组去重

javascript
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

javascript
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

javascript
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 函数。

javascript
arr.reduceRight(callback(accumulator, currentValue[, index[, array]])[, initialValue])

8.1 拓展使用

8.1.1 运行一个带有回调每个函数将其结果传给下一个的异步函数列表

javascript
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 定义可组合函数

组合函数的概念简单,它只是简单地结合了多个函数。它是一个从右向左流动的函数,用上一个函数的输出调用每个函数。

javascript
/**
 * 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() 来确定某属性是否是对象本身的属性。

javascript
// 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

可迭代对象 上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。

javascript
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 循环之间的区别。

javascript
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() 方法返回数组中满足提供的测试函数的第一个元素的值。

javascript
arr.find(callback[, thisArg])
  • 数组中第一个满足所提供测试函数的元素的值,否则返回  undefined
javascript
var arr = [1, 2, 3, 4, 5]
var found = arr.find(function (element, index, array) {
  return element > 3
})
console.log(found) // 4
  • callback 函数会为数组中的每个索引调用即从 0length - 1,而不仅仅是那些被赋值的索引,这意味着对于稀疏数组来说,该方法的效率要低于那些只遍历有值的索引的方法。
javascript
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 了。
javascript
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,并根据这个新值做出判断。

javascript
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() 方法返回数组中满足提供的测试函数的第一个元素的索引。

javascript
arr.findIndex(callback[, thisArg])
  • 数组中第一个满足所提供测试函数的元素的索引,否则返回  -1
  • callback 函数会为数组中的每个索引调用即从 0length - 1,而不仅仅是那些被赋值的索引,这意味着对于稀疏数组来说,该方法的效率要低于那些只遍历有值的索引的方法。
  • 如果数组中一个尚未被 callback 函数访问到的元素的值被 callback 函数所改变,那么当 callback 函数访问到它时,它的值是将是根据它在数组中的索引所访问到的当前值;被删除的元素仍旧会被访问到,但是其值已经是 undefined 了。

其他用法与 find() 方法相同。

13. entries

entries() 方法返回一个新的  Array Iterator  对象,该对象包含数组每个索引的键值对。

javascript
var arr = ['a', 'b', 'c']
var iterator = arr.entries()
console.log(iterator) // Array Iterator [0, 'a'], [1, 'b'], [2, 'c']
  • 数组迭代器中存储的是原数组的地址,而不是数组元素值。
javascript
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']
  • 如果数组中元素改变,那么迭代器的值也会改变
javascript
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 值。
javascript
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 对象。

javascript
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() 方法也会遍历稀疏数组的空位,并返回这些空位的索引。
javascript
const sparseArray = [1, , 3]

for (const key of sparseArray.keys()) {
  console.log(key)
}
// 0
// 1
// 2
  • keys() 方法返回的迭代器对象是不可变的,不能被修改。
javascript
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.valuesArray.prototype[Symbol.iterator]  的默认实现。
javascript
Array.prototype.values === Array.prototype[Symbol.iterator] // true
  • 数组迭代器中存储的是原数组的地址,而不是数组元素值。
javascript
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"
  • 如果数组中元素改变,那么迭代器的值也会改变
javascript
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 值。
javascript
const sparseArray = [1, , 3]
for (const value of sparseArray.values()) {
  console.log(value)
}
// 1
// undefined
// 3

17. 迭代器

迭代器是一种特殊的对象,它实现了两个方法:

  1. next() 方法:返回一个对象,该对象包含两个属性:done 和 value。done 属性是一个布尔值,表示迭代是否完成;value 属性是迭代返回的下一个值。当 done 为 true 时,迭代结束。
  2. return() 方法:结束迭代并返回一个值。

迭代器对象可以被用来遍历某些数据结构,如数组、Map、Set、字符串、TypedArray、arguments 对象等。

javascript
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() 方法返回一个包含 donevalue 属性的对象。done 属性是一个布尔值,表示迭代是否完成;value 属性是迭代返回的下一个值。当 donetrue 时,迭代结束。

javascript
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() 方法,用来返回一个包含 donevalue 属性的对象。
  • 生成器是一种函数,它使用 yield 关键字来返回一个值,并且可以暂停执行并恢复执行。

生成器与迭代器的关系类似于函数与函数调用的关系。生成器可以被调用,返回一个迭代器对象。

javascript
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、扩展运算符 (...) 等语句迭代。

javascript
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]

君子慎独