es6 for...of循环 和 Iterator

传统遍历的方式以及缺陷

for循环
	let arr = [1,2,3,4,5,6];
	for(let i=0;i<arr.length;i++){
		.....
	}

缺陷:代码不够简洁

forEach循环
	let arr = [1,2,3,4,5,6];
	arr.forEach(function(value,index){
		.....
	})

缺陷:无法中断停止整个循环

for…in循环
	let arr = [1,2,3,4,5,6];
	for(let i in arr){
		.....
	}

缺陷:for…in循环常用于对象的循环遍历,不推荐用于遍历数组。 如果用于数组遍历就得注意,例如上述代码,for…in循环中的i是字符串类型,而不是数字类型


for…of

for...of是一种用于遍历数据结构(可遍历:数组、对象、字符串、set和map结构等具有Iterator接口的数据结构)的方法

	let arr = [1,2,3,4,5,6];
	for(let val of arr){
		console.log(val); // 1 2 3 4 5 6
	}

优势:写法比for循环简洁很多

	let arr = [1,2,3,4,5,6];
	for(let v of arr){
		if(v === 3){
			//终止掉整个循环
			break;
		}
		console.log(v); // 1 2
	}

优势:可以用break终止整个循环

	let arr = [1,2,3,4,5,6];
	for(let v of arr){
		if(v === 3){
			//跳过当前循环,并继续后面的循环
			continue;
		}
		console.log(v); // 1 2 4 5 6
	}

优势:可以用continue跳出当前循环,并继续后面的循环

	let arr = [1,2,3,4,5,6];
	//值
	for(let v of arr.values()){
		console.log(v); // 1 2 3 4 5 6
	}
	//索引
	for(let i of arr.keys()){
		console.log(i); // 0 1 2 3 4 5
	}
	//索引 + 值
	for(let [i,v] of arr.entries()){
		console.log('索引'+i,'值'+v); 
			// 索引0 值1
			// 索引1 值2
			// 索引2 值3
			// 索引3 值4
			// 索引4 值5
			// 索引5 值6
	}

优势:for…of循环可以直接获取到键、值

for…of还支持字符串、类数组、set和map解构的遍历,这里就偷个小懒不一一举例了

注意:for…of不可以遍历Object对象,不信的话可以自己写写试试,它会报错的。
Why???为什么数组,Set和Map结构又可以支持for…of的遍历呢?

要想被for…of正常遍历,都需要实现一个遍历器Iterator,而数组、Set和Map结构内置就有遍历器Iterator(又可以叫迭代器),在它们的原型中有Symbol.iterator方法,而Object对象并没有实现这个接口所以不能用for…of遍历

	//数组
	Array.prototype[Symbol.iterator]; // function values(){...}
	//字符串
	String.prototype[Symbol.iterator]; // function [Symbol.iterator](){...}
	//Set结构
	Set.prototype[Symbol.iterator]; // function values(){...}
	//Map结构
	Mao.prototype[Symbol.iterator]; // function entries(){...}
	//Object对象
	Object.prototype[Symbol.iterator]; // undefined

Symbol.iteratorSymbol 对象iterator 属性,是一个特殊的Symbol值,因此,当它作为prototype对象属性名的时候,获取它的时候需要使用[ ]的形式: prototype[Symbol.iterator]不能使用点形式获取prototype.Symbol.iterator。原因是形式会把后面的值当作是字符串类型处理,而不是Symbol类型。

拥有[Symbol.iterator]()方法的数据结构,可以被for…of遍历(可遍历对象)

Iterator原理

可遍历对象for...of遍历的时候,[Symbol.iterator]()就会被调用,返回一个iterator对象

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

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

	//数组(可遍历对象)
	let arr = ['a','b','c','d'];
	//调用数组的Symbol.iterator()方法
	let iter = arr[Symbol.iterator]();

	iter.next(); // {value:'a',done:false}
	iter.next(); // {value:'b',done:false}
	iter.next(); // {value:'c',done:false}
	iter.next(); // {value:'d',done:false}
	iter.next(); // {value:undefined,done:true}
for…of原理

先调用可遍历对象的[Symbol.iterator]() 方法,得到一个iterator遍历器对象,然后就在遍历器上不断调用next(),直到done的值为true的时候,就代表遍历完成结束了


自定义Iterator

一个对象如果要具备可被for...of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署/添加遍历器生成方法

	//定义一个Object对象
	let obj = {
		0:'a',
		1:'b',
		2:'c',
		length:3,
		//添加[Symbol.iterator]方法
		[Symbol.iterator]:function(){
			let _this = this;
			let index = 0;
			return {
				next:()=>{
					let value = _this[index];
					let done = (index >= _this.length);
					index++;
					return { value,done }
				}
			}
		}
	};

	//试一下for...of
	for(let v of obj){
		console.log(v); 
		// a
		// b
		// c
	}

这个例子就是说我们可以创建一个可遍历的对象,并且自定义它的遍历行为。或者说可以通过添加[Symbol.iterator]()方法,把一个不可遍历的Object对象,变成可遍历的对象

类似数组的对象,本来就具有遍历接口,可以直接遍历。将它的遍历接口改成数组的Symbol.iterator属性

	let obj = {
		0:'a',
		1:'b',
		2:'c',
		length:3,
		[Symbol.iterator]:Array.prototype[Symbol.iterator]
	};

	for(let v of obj){
		console.log(v);
		// a
		// b
		// c
	}

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

	let obj = {
		a:'a',
		b:'b',
		c:'c',
		length:3,
		[Symbol.iterator]:Array.prototype[Symbol.iterator]
	};

	for(let v of obj){
		console.log(v);
		// undefined
		// undefined
		// undefined
	}

调用Iterator的场合

除了for...of循环,还有几个别场合会默认调用Symbol.iterator方法

  • 解构赋值

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

    	let set = new Set().add('a').add('b').add('c');
    
    	let [x,y] = set; // x='a',y='b'
    
    	let [start,...end] = set; // start='a',end=['b','c']
    
  • 扩展运算符

    扩展运算符(...)也会调用默认的 Symbol.iterator方法。

    	let str = 'hello';
    	[...str]  //['h','e','l','l','o']
    
    
    	let arr = ['a','b','c'];
    	[...arr,'d']  //['a','b','c','d']
    
  • yield*

    yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口

    	let generator = function* (){
    		yield 1;
    		yield* [2,3,4];
    		yield 5;
    	}
    
    	let iterator = generator();
    	iterator.next() // { value: 1, done: false }
    	iterator.next() // { value: 2, done: false }
    	iterator.next() // { value: 3, done: false }
    	iterator.next() // { value: 4, done: false }
    	iterator.next() // { value: 5, done: false }
    	iterator.next() // { value: undefined, done: true }
    
  • 其他场合

    由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。

    • for…of
    • Array.from()
    • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])
    • Promise.all()
    • Promise.race()

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值