什么是可迭代对象
可迭代对象指的是可被迭代的对象, 可以使用for...of或者...,就像对数组使用for...of会得到每一个数组元素的值, 对map使用for...of会得到每一个键值对组成的数组
let m = new Map()
m.set('1', 'hello')
m.set('2', 'world')
for(let value of m) {
console.log(value)
}
// ['1', 'hello']
// ['2', 'world']
可以这样被遍历的对象就是可迭代对象
可迭代对象必须满足可迭代协议,即具备一个属性 —— Symbol.iterator,它是一个函数, 返回一个调用者的迭代器对象, 其中具备next()方法,这个next()调用时,会返回一个对象,其类型如下:
{
done: Boolean,
value: Any
}
done: 表示是否已经结束迭代value: 表示当前迭代到的值
Array、Set、Map天生实现了迭代器方法
Array的迭代器
let arr = [1,2,3]
// 获取数组arr的迭代器
let iter = arr[Symbol.iterator]()
iter.next(); // { value: 1, done: false }
iter.next(); // { value: 2, done: false }
iter.next(); // { value: 3, done: false }
iter.next(); // { value: undefined; true }
Set的迭代器
let s = new Set()
s.add(1);
s.add(2);
let iter = s[Symbol.iterator]()
iter.next(); // { value: 1, done: false }
iter.next(); // { value: 2, done: false }
iter.next(); // { value: undefined; true }
Map的迭代器
let m = new Map()
m.set('1', 'hello');
m.set('2', 'world');
let iter = m[Symbol.iterator]()
iter.next(); // { value: ['1', 'hello'], done: false }
iter.next(); // { value: ['2', 'world'], done: false }
iter.next(); // { value: undefined, done: true }
所以这三者都可以使用 for...of 遍历。但是 Object 由于没有实现 Symbol.iterator,所以 Object 先天无法使用 for...of, 如果想要使用 for...of, 那么需要手动在Object上实现Symbol.iterator
在Object上实现Symbol.iterator
根据上面对 Symbol.iterator 的分析,我们可以得到其运作流程:
- 被迭代时,自动触发并执行
Symbol.iterator设置的函数,拿到迭代器, 我们命名为iter - 每迭代一次,执行迭代器的
next()方法,运行其中的逻辑后,返回一个对象{ value: xxx, done: yyy } - 返回其中的
value, 供用户使用
于是我们可以用以下方法让Object可迭代
let obj = {
foo: 1,
bar: 2,
// 定义迭代器, 迭代器不接受参数, 不然你打一下会发现是undefined
[Symbol.iterator](){
let keys = Object.keys(this)
// 通过索引控制每一次迭代返回的value
let curIndex = 0
return {
// 每次迭代自动触发next()方法
next(){
if(curIndex < keys.length) {
return {
value: obj[keys[curIndex++]],
done: false
}
}else {
return {
value: undefined,
done: true
}
}
}
}
}
}
for(let i of obj) {
console.log(i)
}
手写 Object.values
利用迭代器
把迭代器写到 Object 的原型上
Object.prototype[Symbol.iterator] = function() {
let keys = Object.keys(this)
let curIndex = 0
let self = this
return {
next() {
if(curIndex < keys.length) {
return {
value: self[keys[curIndex++]],
done: false
}
}else {
return {
value: undefined,
done: true
}
}
}
}
}
然后再写自己的Object.values()
Object.prototype.myValues = function(obj) {
let res = []
for(let v of obj) {
res.push(v)
}
return res
}
obj = {
a: 1,
b: 2,
c: 3
}
Object.myValues(obj)
兼容版
Object.prototype.myValues = function(obj) {
let res = []
if(obj === null) {
return res
}
// 如果传入的对象实现了Symbol.iterator, 那么就使用for...of
if(typeof obj[Symbol.iterator] === 'function') {
for(let v of obj) {
res.push(v)
}
return res
}
// 没有迭代器那就使用for...in
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
res.push(obj[key])
}
}
return res
}
生成器
生成器函数(Generator Functions)是 ECMAScript 6(ES6)引入的一项新特性,它允许你在函数中使用 yield 关键字来暂停和继续函数的执行。生成器函数的定义使用 function* 语法。
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}
和迭代器的关系
执行生成器函数,会返回一个名为生成器的特殊迭代器对象, 这个迭代器与 Iterator 不同的是,它不是连续执行的,它每一次执行,都会暂停到最近的下一个 yield 处,并返回其后表达式的结果,再一次调用 next(),继续向下执行, 直到执行完毕。
生成器函数带来的迭代是可控的,因为你可以通过设置yield使它暂停在任意的位置并拿到当前的值
let iter = simpleGenerator()
for(let i of iter) {
console.log(i)
}
利用生成器实现for...of
Object.prototype[Symbol.iterator] = function*() {
for (const key in this) {
yield {key, value: this[key]}
}
}
let tempObj = {a: 1, b: 'hello'}
for (const item of tempObj) {
console.log(item.key, item.value)
}