Iteraor 和 for...of循环

Iteraor 和 for…of循环

1.iterator (遍历器) 的概念

它是一种接口,为各种不同的数据结构提供统一的访问机制,任何数据结构,只要部署了Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator的作用:

  • 为各种数据结构提供一个统一的,简单的访问接口
  • 使得数据结构的成员能够按照某种次序排列
  • 供for…of消费

Iterator遍历过程:

  • 创建一个指针对象,指向当前数据结构的起始位置。遍历器本质上是一个指针对象。
  • 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
  • 第二次调用指针对象的next方法,指针指向数据结构的第二个成员
  • 不断调用指针的next方法,直到它指向数据结构的结束位置

每次调用next方法都会返回数据结构的当前成员信息。具体来说,就是返回value和done俩个属性的对象,value属性是当前成员的值,done是一个布尔值,表示遍历是否结束。

2.默认 Iterator 接口

当使用for…of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。

数据结构只要部署了Iterator接口,我们就称这种数据结构为"可遍历"的。

ES6规定,默认的Iterator接口的部署在数据结构的Symbol.iterator属性,调用Symbol.iterator方法,我们就会得到当前数据结构默认的遍历器生成函数。Symbol.iterator本身是一个表达式,返回Symbol对象的Iterator属性。

原生具有Iterator接口的数据结构如下:

  • Array

  • Map

  • Set

  • String

  • TypeArray (类型数组,二进制缓存区)

  • 函数的arguements对象

  • NodeList对象 (是一种类数组对象,用于保存一组有序的节点)

下面是通过遍历器实现指针结构的例子

function Obj(value){
    this.value = value;
    this.next = null;
    //值为传入的参数 next初始化为null
}
//在Obj的原型链上部署 [Symbol.iterator] 方法 
//调用该方法会返回遍历器对象iterator 调用该对象的next方法 在返回一个值的同时将内部指针移到下一个实例
Obj.prototypep[Symbol.iterator] = function(){
    var iterator = {
        next:next
    };
    
    var current = this;
    function next(){
        if(current){
            var value = current.value;
            current = current.next;//将内部指针移到下一个实例
            //返回当前成员的信息
            return {
                done:false,
                value:value
            }
        }else{
            //遍历到结束位置时
            return {
                done:true
            }
        }
    	return iterator;
 }
 var one = new Obj(1);
 var two = new Obj(2);
 var three = new Obj(3);
    
 one.next = two;
 two.next = three;
    for(var i of one){
        console.log(i);//1 , 2 , 3
}
 
    

下面是一个为对象添加Iterator接口的例子(不太理解,感觉只是为对象的data属性添加了接口而不是为对象添加的)

let obj = {
    data: ['hello' , 'world'],
    [Symbol.iterator](){
        const self = this;
        let index = 0;
        return {
            next() {
                //当前下标还在对象data的范围里 返回信息
                if(index < self.data.length){
                    return {
                        value: self.data[index++],
                        done:false
                    };
                } else {
                    return {
                        value: undefined,
                        done:true
                    };
			   }
            }
		}
    }
    
}

对于类似数组的对象(存在数值键名和length属性),部署Iterator接口有一个简便方法,使用Symbol.iterator方法直接引入数组的Iterator。

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]
//或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator]

普通对象部署数组的Symbol.iterator方法并无效果。

如果Symbol.iterator方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。

有了遍历器借口,数据结构就可以使用for…of循环遍历,也可以使用while循环遍历。

3.调用iterator接口的场合

有一些场合会默认调用iterator接口,除了for…of循环,还有几个别的场合。

  • 解构赋值 对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator方法。

  • 扩展运算符(…)也会调用默认的Iterator接口 只要某个数据结构部署了Iterator接口,就可以对它使用扩展运算符,将其转为数组

    let arr = [...iterator];
    
  • yield* 后面跟的是一个可遍历的结构 它会调用该结构的遍历器接口

  • 其他场合

    for…of

    Array.from()

    Map() Set() WeakMap() WeakSet()

    Promsie.all()

    Promise.race()

4.字符串的Iterator接口

字符串是一个类似数组的对象,也具有原生的Iterator接口。

let someString = 'hi';
let iterator = someString[Symbol.iterator]();
iterator.next(); //{ value : h ,done:false}
iterator.next(); //{ value : i ,done:false}
iterator.next(); //{ value : undefined ,done:true}

可以覆盖原生的Symbol.iterator方法达到修改遍历器行为的目的。

let str = new String('hi');
[...str] //[ 'h' , 'i']
str[Symbol.iterator] = function(){
    return {
        next : function(){
            if(this._first){
                this._first = false;
                return {
                    value: "bye",
                    done: false,
				}
			} else {
                return { done : true};
            }
		},
        _first:true
        
	};
};
[...str] //["bye"]
str//'hi'

上面的代码,字符串的Symbol.iterator方法被改变了,由于扩展运算符默认调用iterator接口,所以当再次使用扩展运算符时,返回的结果变成了"bye"。

5.Iterator接口与Generator函数

Symbol.iterator方法的最简单实现 Generator函数

只需要用yield命令给出每一步的返回值即可。

let myIterator = {};
myIterator[Symbol.iterator]  = function* (){
    yield 1;
    yield 2;
    yield 3;
}
[...myIterator]// 1 2 3
//或者采用下面的简洁方法
let obj = {
    *[Symbol.iterator](){
        yield 'hello';
        yield 'worle';
	}
}
for(let i of obj){
    console.log(i);
}
// hello
//world

6.遍历器对象的return()和throw()方法

如果自己部署接口,next方法必须有,return,throw方法是可选的。

return方法的使用场合是,for…of循环提前退出(通常是因为出错,break,continue),就会调用return方法。如果一个对象在完成遍历前需要清理或释放资源,就可以部署return方法。return方法返回必须是一个对象。

function readLinesSync(file){
    return {
        next(){
            return { done: true};
		},
        return(){
            file.close();
            return {done : true};
        },
    }
}
for(let line of readLinesSync(fileName)){
    console.log(line);
    break;//break提前退出  会调用return方法
}

7.for…of循环

7.1 数组

for…of循环本质上就是调用iterator接口产生的遍历器。

for…of循环可以替代数组的forEach方法。

for…in循环只能获得对象的键名,for…of循环允许遍历获得键值。但是for…of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。与for…in不同。

let arr = [3,5,7];
arr.foo = 'hello';
for(let i in arr){
    console.log(i);// 0 1 2 foo
}
for(let i of arr){
    console.log(i);// 3 5 7
}

7.2 Set和Map结构

  • 遍历的顺序是按照各个成员被添加进数据结构的顺序
  • Set结构遍历时返回的是一个值,Map结构遍历时返回的是一个数组,该数组的俩个成员分别为键名和键值。

7.3 计算生成的数据结构

有些数据结构是在现有数据结构的基础上计算生成的。比如数组,set,map。都部署了以下三个方法,调用后生成的遍历器对象所遍历的都是计算生成的数据结构。

  • entries() [ 键名 ,键值 ]
  • keys()
  • values()

7.4 类似数组的对象

对于字符串,for…of可以正确识别32位UTF-8字符。

并不是所有的类似数组都有Iterator接口,可以调用Array.from方法将其转换为数组。

7.5 对象

普通的对象for…of不能直接使用,会报错。但是for…in可以用来遍历键名。

解决办法:

  • 使用Object.keys()方法将对象的键名生成一个数组。遍历这个数组。

    for(let key of Object.keys(obj)){
        console.log(key + ':' + obj[key]);
    }
    
    
  • 使用Generator函数包装一下

    function* entries(obj){
        //用yield命令给出每一步的返回值即可
        for(let key of Object.keys(obj)){
            yield [key , obj[key]];
        }
    }
    for(let [key , value] of entries(obj)){
        console.log(key,' ',value);
    }
    

7.6 遍历语法比较

  • for循环,比较麻烦;
  • forEach() 无法中途退出,break,continue都不能奏效。
  • for…in 数组的键名是数字,for…in是以字符串作为键名;for…in不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键;某些情况下,遍历顺序随意。
  • for…of的优点 语法简洁,没有for…in那些缺点;可以与break,continue,return配合使用;提供了遍历所有数据结构统一的接口。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值