正题如下:
问:有没有遇到有人问你对象为什么不能用for … of … 遍历?
答:没错,我遇到了,虽然我回答不能,但是发现通过某些修改后还是可以使用的
有没有发现对象原型并没有那个可遍历的方法
所以对象不能使用for of 去遍历的原因:对象不是可迭代的
但是对ES6有过了解的,都知道一个函数,那就是生成器和迭代器,那具体是干什么的?
首先我先说下如何 让对象可以使用 for ... of ..
来遍历出 key 值
let obj = {
name: "fqniu",
age: 25,
gender: "boy",
from: "china"
}
// for(var val of obj){
// console.log(val)
// }
// 会直接报错:obj is not iterable
// 先创建一个生成器 create 注意返回一个迭代器
function* create(o) {
for (var key in o) {
yield o[key]
}
}
Object.defineProperty(obj, Symbol.iterator, {
value(){
return create(obj)
}
})
for(var val of obj){
console.log(val)
}
// 打印出key值
let obj = {
name: "fqniu",
age: 25,
gender: "boy",
from: "china"
}
// for(var val of obj){
// console.log(val)
// }
// // 报错:obj is not iterable
// function* create(o) {
// for (var key in o) {
// yield o[key]
// }
// }
// 或者:
function* create(o) {
let idx = 0
let length = Object.keys(o).length
for (var key in o) {
idx++
yield { value: o[key], done: idx === length }
}
}
Object.defineProperty(obj, Symbol.iterator, {
value() {
return create(obj)
}
})
for (var val of obj) {
console.log(val) // 打印如下
}
以下引用 阮一峰的ES6
Generator 函数是 ES6 提供的一种异步编程解决方案;
- generator 生成器函数,返回一个迭代器(遍历器可以依次遍历generator函数内部的每一个状态)
- yield 暂停 / return 结束
Iterator 是迭代器 (遍历)
- next() 返回格式 {value,done} 的对象,value:迭代到当前位置的值,done:迭代器是否迭代完毕;
- for… of … 内部会自动调用 迭代器的 next()方法;就是可以自动遍历generator函数运行时生成的 Iterator对象,且此时不再需要调用next方法;
function* show(){
console.log('start')
yield 10
console.log(1)
yield 20
console.log(2)
return 100
console.log('end')
}
上面代码定义了一个 Generator函数show,它内部有两个yield表达式(hello和world),即该函数有四个状态:10,20,100 和 return语句(结束执行)。
然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
yield表达式
由于generator函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供一种暂停执行的函数,yield表达式就是暂停标志;
遍历器对象的next方法的运行逻辑如下:
- 遇到yield表达式,就暂停执行后面的操作,并将近跟着yield后面的那个表达式的值,作为返回的对象的value属性值
- 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
- 如果没有再遇到yield表达式,就一直运行到函数结束,直到return 语句为止,并将return 语句后面的表达式的值,作为返回的对象的value属性值
- 如果该函数没有return 语句,则返回的对象value属性值为undefined
next方法的参数
yield表达式本身没有返回值,或者说总是返回undefined,next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值;
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
注意:yield本身没有返回值,也就是说 var y = 2 * (yield (5+1)) ,
var y = 2 * (yield 6) 而yield 6 没有返回值,或者说返回undefined ,
所以导致 y的值 等于 2 * undefined 即是NaN
引用:
上面代码中,第二次运行next方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),除以 3 以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5+ NaN + undefined,即NaN。如果向next方法提供参数,返回结果就完全不一样了。上面代码第一次调用b的next方法时,返回x+1的值6;第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y /3的值8;第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。
注意,由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。
迭代器Iterator
JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。
Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
如果想要深入可以参考阮一峰的ECMAScript 6 入门
阮一峰的ECMAScript 6 入门 的Iterator 和for … of 循环