JS中让对象支持for...of遍历

1.for…of的工作原理

for…of 循环首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next() 方法来遍历所有返回值。

  • 数组可以直接使用for…of遍历是因为数组内置了迭代器

2.让对象支持for…of

  • 让对象支持for…of的办法就是手动给对象添加迭代器
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,这是两个不同的概念

从上面的编码可以看出,给一个对象定义迭代器的步骤如下:

  • 1.给对象添加一个名称为Symbol.iterator的属性方法
  • 2.这个方法必须返回一个迭代器对象,它的结构必须如下:
{
	next: function() {
		return {
			value: any, //每次迭代的结果
			done: boolean //迭代结束标识
		}
	}
}

done为true时候遍历结束Symbol.iterator是一个内置符号

3.可复用的对象迭代器添加(通过原型委托)

想一想,如果有很多对象(但不是所有对象都需要)都想要使用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通过原型链可以找到原来的原型,丢失的问题也就解决了

4.让所有对象支持for…of

如果你想所有对象都支持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

5.for…of原理模拟

针对添加过迭代器的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)
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张驰Terry

知识源于创作热情,感谢你的支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值