概述
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配合使用;提供了遍历所有数据结构统一的接口。
最后
以上就是义气饼干为你收集整理的Iteraor 和 for...of循环的全部内容,希望文章能够帮你解决Iteraor 和 for...of循环所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复