为什么需要迭代器
用循环语句迭代数据时,必须要初始化一个变量来记录每次迭代在数据集合中的位置,而在许多编程语言中,已经开始通过程序化的方式用迭代器对象返回迭代过程中集合的每一个元素。迭代器的使用可以极大地简化数据操作,于是ECMAScript6也向JavaScript中添加了这个迭代器特性。新的数组方法和新的集合类型(例如Set集合与Map集合)都依赖迭代器的实现,for-of循环、展开运算符(...。),甚至连异步编程都可以使用选代器。
什么是迭代器
迭代器是一种特殊的对象,所有迭代器对象都有一个next()
方法,每次调用会返回一个结果对象。结果对象有两个属性:
-
value
: 表示下一个将要返回的值 -
done
:布尔类型,有更多可返回数据时返回false
,否则返回true
如果在最后一个返回后在调用next()
方法,done
值为true
,value
则包含迭代器最终返回的值,这个返回值不是数据集的一部分,与函数的返回值类似,是函数调用过程中最后一次给调用者传递信息的方法,没有相关数据则返回undefined
。
我们现在可以用ES5语法创建一个迭代器:
function createIterator(items) {
var i = 0
return {
next: function() {
var done = (i >= items.length)
var value = !done ? items[i++] : undefined
return {
value: value,
done: done
}
}
}
}
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 }
// 之后的调用都是返回相同的内容
console.log(iterator.next())
看起来稍微复杂,ES6引入了一个生成器对象,它可以让创建迭代器对象的过程变的更简单。
什么是生成器
生成器是一种返回迭代器的含税,通过function关键字后的星号(*)来表示,函数中还会用到新的的关键字yield
。
// function *createIterator() {
// yield 1
// yield 2
// yield 3
// }
function *createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i]
}
}
const 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 }
生成器函数也可以用下面的方式创建:
let createIterator = function *(items){}
给对象添加生成器方法:
let o = {
*createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i]
}
}
}
const iterator = o.createIterator([1, 2, 3])
注意:不能用箭头函数创建生成器
可迭代对象和for-of循环
可迭代对象有Symbol.iterator
属性。在ES6中,所有的集合对象(数组、Set集合和Map集合)和字符串都是可迭代对象,这些对象都有默认的迭代器。
for-of循环每执行一次都会调用可迭代对象的next()
方法,并将迭代器返回的结果对象的value
属性存储在一个变量中,循环在结果对象的done
属性为true
时结束。
const arr = [1, 2, 3]
for (let item of arr) {
console.log(item)
}
for-of 用于不可迭代对象、null、undefined时,会抛出错误。
访问默认迭代器
可以通过Symbol.iterator
来访问对象默认的迭代器:
const arr = [1, 2, 3]
let iterator = arr[Symbol.iterator]()
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 }
检测对象是否为可迭代对象:
function isIterator(obj) {
return typeof obj[Symbol.iterator] === 'function'
}
创建可迭代对象
我们创建如下对象,就是一个不可迭代对象:
let obj = {
a: 1,
b: 2,
c: 3
}
如何使其变成一个可迭代对象呢,代码如下:
let obj = {
a: 1,
b: 2,
c: 3,
*[Symbol.iterator]() {
let keys = Object.keys(this)
for (let i = 0; i < keys.length; i++) {
yield [keys[i], this[keys[i]]]
}
}
}
for (const iterator of obj) {
console.log(iterator)
}
// 输出如下
/**
[ 'a', 1 ]
[ 'b', 2 ]
[ 'c', 3 ]
**/
内建迭代器
集合对象迭代器
- 数组、Map集合、Set集合都有如下三种迭代器:
-
entries()
返回一个数组,分别表示每个元素的键和值。- 数组:第一元素是数字类型的索引
- Set集合:第一个元素和第二个元素都是值
- Map集合:第一元素是键名
-
values()
返回集合中所存的所有值。 -
keys()
返回集合中存在的每一个键。
- 不同集合类型的默认迭代器, 在
for-of
循环中,如果没有显示的指定则使用默认的迭代器。
- 数组和Set集合的默认迭代器是
values()
- Mpa的默认迭代器是
entries()
// 数组
let arr = [1, 2, 3]
for (let item of arr) {
console.log(item)
}
// set集合
let set = new Set([1,2, 3])
for (let item of set) {
console.log(item)
}
// map集合
let map = new Map()
map.set('name', 'abc')
map.set('age', 12)
for (let [key, value] of map) {
console.log(`${key}=${value}`)
}
字符串迭代器
let str = 'abcdef'
for (let ch of str) {
console.log(ch)
}
高级迭代器功能
给迭代器传递参数
如果给迭代器的next()
方法传递参数,那么这个参数的值就会替代生成器内部上一条yield语句的返回值。
function *createIterator() {
let first = yield 1
let second = yield first + 2 // 10 + 2
yield second + 3 // 20 + 3
}
const iterator = createIterator()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next(10)) // { value: 12, done: false }
console.log(iterator.next(20)) // { value: 23, done: false }
console.log(iterator.next()) // { value: undefined, done: true }
在迭代器中抛出错误
function *createIterator() {
let first = yield 1
let second
try {
second = yield first + 2 // 10 + 2
} catch {
second = 20
}
yield second + 3 // 20 + 3
}
const iterator = createIterator()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next(10)) // { value: 12, done: false }
console.log(iterator.throw(new Error('Some Error'))) // { value: 23, done: false }
console.log(iterator.next()) // { value: undefined, done: true }
生成器返回语句
function *createIterator() {
yield 1
return
yield 2
yield 3
}
const iterator = createIterator()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: undefined, done: true }
console.log(iterator.next()) // { value: undefined, done: true }
console.log(iterator.next()) // { value: undefined, done: true }
return 也可以指定返回值
function *createIterator() {
yield 1
return 10
}
const iterator = createIterator()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 10, done: true }
console.log(iterator.next()) // { value: undefined, done: true }
委托生成器
在某些情况下,我们需要将两个生成器合二为一。
function *createNumberIterator() {
yield 1
yield 2
return 3
}
function *createColorIterator() {
yield 'blue'
yield 'red'
}
function *createCombinedIterator() {
yield *createNumberIterator()
yield *createColorIterator()
yield true
}
const iterator = createCombinedIterator()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: 'blue', done: false }
console.log(iterator.next()) // { value: 'red', done: false }
console.log(iterator.next()) // { value: true, done: false }
console.log(iterator.next()) // { value: undefined, done: true }
异步任务执行
function run(taskDef) {
let task = taskDef()
// 开始执行任务
let result = task.next()
function step() {
if (!result.done) {
if (result.value instanceof Promise) {
result.value.then((value) => {
result = task.next(value)
step()
}).catch((reason) => {
task.throw(reason)
})
} else {
result = task.next(result.valuve)
step()
}
}
}
step()
}
function fetchData(isError = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!isError) {
resolve('ok')
} else {
reject('error')
}
}, 2000)
})
}
run(function*() {
try {
let msg1 = yield fetchData()
console.log(msg1)
let msg2 = yield fetchData()
console.log(msg2)
yield fetchData(true)
} catch (err) {
console.log('出错啦:', err)
}
})
async/await:Promise和生成器的语法糖
网友评论