如果要从一个数据集中获取一个数据项,可以对这个数据集进行迭代。
JavaScript 提供了许多迭代集合的方法,从简单的?for
?循环到?map()
?和?filter()
。本节要介绍的迭代器也是一种方案,并且迭代器将迭代的概念直接带入核心语言,同时提供了一种机制来自定义?for...of
?循环的行为。
迭代器是一种特殊对象,它符合迭代器协议规范。在 TypeScript 中,我们可以定义一个接口,这个接口上有一个函数类型?next
?,next()
?方法的返回值类型是?{ value: any, done: boolean }
。其中,value
?是 any 类型,表示下一个将要返回的值;done
?是布尔类型,当没有更多可返回数据时返回 true。迭代器还会保存一个内部指针,用来指向当前集合中值的位置。
迭代器一旦创建,迭代器对象就可以通过重复调用?next()
?显式地迭代。
实例演示
interface IteratorInterface {
next: () => {
value: any
done: boolean
}
}
function createIterator(array: any[]): IteratorInterface {
let index = 0
let len = array.length
return {
next: function () {
return index < len ? { value: array[index++], done: false } : { value: undefined, done: true }
}
}
}
var iterator = createIterator([1, 2, 3])
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: 3, done: false }
console.log(iterator.next()) // { value: undefined, done: true }
代码解释:
第 1 行,声明了一个 Iterator 接口,具有 next 这样一个函数类型。
第 8 行,声明了一个可以返回迭代器对象的函数,这个函数的返回值类型必须符合 Iterator 接口。
倒数第 4 行,通过调用迭代器对象上的 next() 方法,可以拿到数据集中的下一个数据项。
最后一行,拿到数据集中的所有数据后,done 属性变为 true。
上面的例子,用模拟的迭代器地迭代了一个数组对象,那是不是所有的对象都可以这样迭代呢?当然不是。
只有一个对象实现了?Symbol.iterator
?属性时,我们才认为它是可迭代的。一些内置的类型如?Array,Map,Set,String,Int32Array,Uint32Array
?等都已经实现了各自的?Symbol.iterator
。
Symbol.iterator
?属性本身是一个函数,就是当前数据结构默认的迭代器生成函数。执行这个函数,就会返回一个迭代器。
比如,String
?是一个内置的可迭代对象:
let str: string = 'Hi'
console.log(typeof str[Symbol.iterator]) // function
String
?的默认迭代器会依次返回该字符串的字符:
实例演示
let str: string = 'Hi'
let iterator: IterableIterator<string> = str[Symbol.iterator]()
console.log(iterator.next()) // { value: 'H', done: false }
console.log(iterator.next()) // { value: 'i', done: false }
console.log(iterator.next()) // { value: undefined, done: true }
代码解释:
第 1 行,声明一个字符串类型变量,字符串类型内置了默认迭代器生成函数?Symbol.iterator
。
第 2 行,执行这个函数,返回了一个迭代器。
总结一下迭代器的作用:
for..of
?循环。for...of
?会遍历可迭代的对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等),调用对象上的?Symbol.iterator
?方法。
实例演示
let iterable = [10, 20, 30]
for (const value of iterable) {
console.log(value)
}
// 10
// 20
// 30
解释:?通过?for...of
?循环遍历数组?iterable
?的每一项元素。
实例演示
const heroes = [
{
name: '艾希',
gender: 2
},
{
name: '泰达米尔',
gender: 1
}
]
for (let { name } of heroes) {
console.log(name)
}
解释:?通过?let { name } of heroes
?循环迭代?heroes
?对象数组,将每一个对象解构,得到每一个对象的?name
?属性值。
实例演示
let iterable = 'mybj'
for (const s of iterable) {
console.log(s)
}
// m
// y
// b
// j
字符串具有可迭代性,通过?for...of
?可以快速遍历出每一个字符。
实例演示
let iterable = new Map()
iterable.set('a', 1)
iterable.set('b', 2)
iterable.set('c', 3)
for (let entry of iterable) {
console.log(entry)
}
// ['a', 1]
// ['b', 2]
// ['c', 3]
for (let [key, value] of iterable) {
console.log(value)
}
// 1
// 2
// 3
解释:?一个 Map 对象在迭代时会根据对象中元素的插入顺序来进行。for...of
?循环在每次迭代后会返回一个形式为?[key,value]
?的数组。通过使用?let [key, value]
?这种解构形式,可以快速获取每一项属性值。
for...of
?语句遍历可迭代对象定义要迭代的数据。for...in
?语句以任意顺序迭代对象的可枚举属性。实例演示
let iterable: number[] = [3, 5, 7]
for (let i in iterable) {
if (iterable.hasOwnProperty(i)) {
console.log(i)
}
}
// 0
// 1
// 2
for (let i of iterable) {
console.log(i)
}
// 3
// 5
// 7
for...in
?可以操作任何对象,迭代对象的可枚举属性。但是?for...of
?只关注于可迭代对象的值。
对数组和 Set 结构进行解构赋值时,会默认调用 Symbol.iterator 方法:
let [head, ...tail] = [1, 2, 3, 4]
// tail = [2, 3, 4]
扩展运算符也会调用默认的 Iterator 接口,得到一个数组结构:
let arr = [...'']
console.log(arr) // ['m','y','b','j']
本小节介绍了迭代器的一些具体使用,要注意?for...of
?与?for...in
?的区别。
另外,可以借助编辑器(如?vscode
)查看一下 TypeScript 迭代器接口定义的源码:
interface IteratorYieldResult<TYield> {
done?: false
value: TYield
}
interface IteratorReturnResult<TReturn> {
done: true
value: TReturn
}
type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>
interface Iterator<T, TReturn = any, TNext = undefined> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...args: [] | [TNext]): IteratorResult<T, TReturn>
return?(value?: TReturn): IteratorResult<T, TReturn>
throw?(e?: any): IteratorResult<T, TReturn>
}
interface IterableIterator<T> extends Iterator<T> {
[Symbol.iterator](): IterableIterator<T>;
}