for…of 循环首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next() 方法来遍历所有返回值。
var myObject = { a: 1, b: 2, c: 3 };
//写法一:简单写法
myObject[Symbol.iterator] = function(){
const _this = this
//也可使用: keys = Object.getOwnPropertyNames(this)
const keys = Object.keys(this)
let index = 0
return {
next(){
return {
value: _this[keys[index++]],
done: index>keys.length
}
}
}
}
//写法二:标准写法,可以指定属性描述符
Object.defineProperty( myObject, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function() {
const _this = this
//也可使用: keys = Object.getOwnPropertyNames(this)
const keys = Object.keys(this)
let index = 0
return {
next(){
return {
value: _this[keys[index++]],
done: index>keys.length
}
}
}
}
});
// 手动遍历 myObject
var it = myObject[Symbol.iterator]();
it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { value:undefined, done:true }
// 用 for..of 遍历 myObject
//不要指望遍历结果总是(1,2,3),因为Object.keys()的无序性
for (var v of myObject) {
console.log( v );
}
// 1
// 2
// 3
拥有迭代器的对象我们叫做iterable (就像上面的myObject),而迭代器叫做iterator,这是两个不同的概念
从上面的编码可以看出,给一个对象定义迭代器的步骤如下:
{
next: function() {
return {
value: any, //每次迭代的结果
done: boolean //迭代结束标识
}
}
}
done为true时候遍历结束
Symbol.iterator是一个内置符号
想一想,如果有很多对象(但不是所有对象都需要)都想要使用for…of怎么办?你可以把前面介绍的为对象添加迭代器的代码封装成函数来复用,没有任何问题,不过下面要介绍的是通过原型委托来复用的写法:
//首先创建一个基于对象原型扩展的iterable,并给它添加一个迭代器
const iterable = Object.create(Object.prototype,{
[Symbol.iterator]: {
enumerable: false,
writable: false,
configurable: true,
value: function() {
const _this = this
//也可使用: keys = Object.getOwnPropertyNames(this)
const keys = Object.keys(this)
let index = 0
return {
next(){
return {
value: _this[keys[index++]],
done: index>keys.length
}
}
}
}
}
})
//使用:
var myObject = { a: 1, b: 2, c: 3 };
var myObject2 = { x: "x", y: "y", z: "z" }
//替换myObject的原型, 使myObject可迭代
//为了不丢失对象myObject原有的原型中的东西
//iterable在创建时将原型设为了Object.prototype
Object.setPrototypeOf(myObject,iterable)
myObject.d = 4
for(let item of myObject){
console.log(item)
}
//1
//2
//3
//4
//使myObject2可迭代
Object.setPrototypeOf(myObject2,iterable)
for(let item of myObject2){
console.log(item)
}
//x
//y
//z
上面的做法有一个问题,就是如果你的myObject已经修改过原型了再调用Object.setPrototypeOf(myObject2,iterable) ,这意味着原来的原型会丢失,下面介绍解决办法:
//定义一个函数用于给obj添加迭代器
function iterable(obj){
if(Object.prototype.toString.call(obj) !== "[object Object]"){
return //非对象,不处理
}
if(obj[Symbol.iterator]){
return //避免重复添加
}
const it = Object.create(Object.getPrototypeOf(obj), {
[Symbol.iterator]: {
enumerable: false,
writable: false,
configurable: true,
value: function() {
const _this = this
//也可使用: keys = Object.getOwnPropertyNames(this)
const keys = Object.keys(this)
let index = 0
return {
next(){
return {
value: _this[keys[index++]],
done: index>keys.length
}
}
}
}
}
})
Object.setPrototypeOf(obj, it)
}
//使用:
var myObject = { a: 1, b: 2, c: 3 };
iterable(myObject)// 让myObject可迭代
myObject.d = 4
for(let item of myObject){
console.log(item)
}
//1
//2
//3
//4
因为创建it时将it的原型指定为了obj的原型( Object.getPrototypeOf(obj) ),然后又将obj的原型指定为了it (Object.setPrototypeOf(obj, it)), 所以obj通过原型链可以找到原来的原型,丢失的问题也就解决了
如果你想所有对象都支持for…of,给每个对象都去添加迭代器是比较繁琐的(即使你像上面那样实现了添加的复用),有一个办法就是直接给对象的原型添加迭代器,要指出的是这样做可能会有一些副作用,Object.prototype位于各种类型的原型链顶端,影响面会非常广,ES6本可以这样做,但是它却没这样做(肯定是有原因的),所以建议按需添加会比较好
//在对象的原型上直接添加迭代器
Object.prototype[Symbol.iterator] = function(){
const _this = this
const keys = Object.keys(this)
let index = 0
return {
next(){
return {
value: _this[keys[index++]],
done: index>keys.length
}
}
}
}
//使用:
var myObject = { a: 1, b: 2, c: 3 };
for(let item of myObject){//这就像myObject本来就支持for...of一样
console.log(item)
}
//1
//2
//3
针对添加过迭代器的myObject,下面代码模拟了for…of的内部原理:
//while版本模拟:
//获得一个myObject的迭代器对象
let it1 = myObject[Symbol.iterator]()
let item1
while(!(item1 = it1.next()).done){
console.log(item1.value)
}
//for版本模拟:
//获得一个myObject的迭代器对象(新的)
let it2 = myObject[Symbol.iterator]()
let item2 = it2.next()
for(; !item2.done; item2 = it2.next()){
console.log(item2.value)
}