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