JavaScript :迭代

迭代(iteration)指的是按照顺序反复执行一段程序的操作,通常会有明确的终止条件。

循环是迭代的基础。循环可以指定迭代的次数和每次迭代执行的操作。

迭代的对象通常是一个有序集合。有序集合,即可以按某种顺序对集合的元素进行遍历的集合。

在开发实践中发现,通过某种特定的数据结构(如数组)来进行迭代,有两个不足:

  • 迭代的方式不统一,迭代前需要知道如何使用相应的数据结构。
  • 迭代的遍历顺序不确定,不同的数据结构往往会有不同的遍历顺序。

在 ES6 之前,执行迭代必须使用循环或其它辅助结构。随着代码量的增加,代码会变得混乱。

很多语言都通过原生语言结构解决了这个问题,开发者无须事先知道具体的迭代方式就能实现迭代。

这个解决方案就是迭代器模式。Python、Java、C++ 等语言都对迭代器模式提供了完备的支持。

从 ES6 开始 ECMAScript 也支持了迭代模式。


主要参考资料:

  • 《JavaScript 高级程序设计(第4版)》- P183(208/931)

迭代器模式

迭代器模式描述了一个方案:实现接口 Iterable 的可迭代对象(iterable)可以通过实现接口 Iterator 的迭代器(iterator)消费(consume)。


接口 Iterable

接口 Iterable 即可迭代协议,实现接口 Iterable 的结构被称为可迭代对象(Iterable)

可迭代对象中拥有可以被遍历的元素。

可迭代对象包含的元素是有限的,而且具有确定的遍历顺序。

可迭代对象(实现接口 Iterable )的要求:

  • 具有支持迭代的自我识别能力
  • 可以创建实现接口 Iterator 的对象,即可以创建迭代器

接口 Iterator

接口 Iterator 即迭代器协议,实现接口 Iterator 的结构被称为迭代器(Iterator)

迭代器从属于可迭代对象,是由可迭代对象创建的,是一次性使用的对象。

迭代器用于迭代与其关联的可迭代对象中的元素,任何可迭代对象都可以被迭代器消费(迭代)。

迭代器要求拥有以下方法:

  • next() ,对可迭代对象中的元素进行有序遍历,获取可迭代对象中的下一个元素的值。
    返回值:对象 IteratorResult(迭代器结果)
  • retrun() (可选),当迭代器提前关闭时被调用。
    返回值:对象 IteratorResult(迭代器结果)

对象 IteratorResult 被称为迭代器结果,是由迭代器的方法 next() 返回的。

迭代器结果拥有两个属性:

  • done ,布尔值,表示可迭代对象是否已经被完整遍历。
  • value ,可迭代对象中的元素的值。

ES6 新增两个高级特性:迭代器、生成器,来支持迭代器模式。


迭代器

迭代器是从属于某个可迭代对象的,是由可迭代对象创建的。所以严格来说,迭代器指的是某个可迭代对象的迭代器。

在 ECMAScript 中实现可迭代对象:

  • 使用一个键为符号 Symbol.iterator 的方法作为默认迭代器(函数)。
  • 符号方法 Symbol.iterator 通常为一个迭代器工厂函数,返回一个迭代器。

ECMAScript 中的迭代器结果:

IteratorResult: { done: boolean, value: any } 

value 的默认值为 undefined 。

当迭代器结果中的 done 为 true 时,表示迭代器对可迭代对象进行了完整的遍历。

此时可迭代对象没有下一个元素,所以此时迭代器结果中的 value 通常为 undefined 。

示例:

  • 迭代器的创建与使用:
    // 定义可迭代对象的类
    class Iterable {
    	constructor(limit) {
    		this.limit = limit
    	}
    	
    	// 定义默认迭代器函数,用于创建迭代器
    	[Symbol.iterator]() {
    		let count = 1,
    			limit = this.limit
    			
    		return {
    			next() {
    				if(count <= limit) {
    					return { done: false, value: count++ }  // 迭代器结果
    				} else {
    					return { done: true, value: undefined }
    				}
    			}
    		}  // 迭代器
    	}
    }
    
    // 创建可迭代对象
    let iterable = new Iterable(2)
    // 创建迭代器
    let iter = iterable[Symbol.iterator]()
    
    // 使用迭代器
    console.log(iter.next())  // { done: false, value: 1 }
    console.log(iter.next())  // { done: true, value: 2 }
    console.log(iter.next())  // { done: false, value: undefined }
    

ECMAScript 的很多内置类型都实现了接口 Iterable :

  • String
  • Array
  • Map
  • Set
  • 对象 argument
  • NodeList 等 DOM 集合类型

示例:

  • 创建并使用 Array 实例对象的迭代器
    // 创建 Array 实例对象
    let arr = [1, 2, 3]
    // 创建迭代器
    let iter = arr[Symbol.iterator]()
    
    // 使用迭代器
    console.log(iter.next())  // { done: false, value: 1 }
    console.log(iter.next())  // { done: true, value: 2 }  
    console.log(iter.next()) // { done: true, value: 3 }
    

如果某个对象的原型链上的父类实现了接口 Iterable ,那么该对象也实现了 Iterable 。


自动迭代

ECMAScript 中有一些原生语法会自动兼容对可迭代对象的迭代。

这些原生语法会自动对可迭代对象进行一次完整迭代,主要过程为:

  1. 创建可迭代对象的迭代器。
  2. 调用迭代器的方法 next() 。
  3. 判断迭代器结果的属性 done 的值。
  4. 如果迭代器结果的属性 done 为 false ,则获取迭代器结果的属性 value 的值。并转到过程 2 。
  5. 如果迭代器结果的属性 done 为 true ,则忽视迭代器结果的属性 value 。
  6. 结束。

自动兼容可迭代对象的原生语法:

  • 语句 for-of
  • 数组解构
  • 扩展操作符
  • Array.from
  • 创建 Set 实例
  • 创建 Map 实例
  • Promise.all() 接收由期约组成的可迭代对象
  • Promise.race() 接收由期约组成的可迭代对象
  • 在生成器中使用操作符 yield*

示例:

class Iterable {
	constructor(limit) {
		this.limit = limit
	}
	
	[Symbol.iterator]() {
		let count = 1,
			limit = this.limit
			
		return {
			next() {
				if(count <= limit) {
					return { done: false, value: count++ }
				} else {
					return { done: true, value: undefined }
				}
			}
		}
	}
}

let iterable = new Iterable(2)

// 自动迭代
for(const item of iterable) {
	console.log(item)
}

// 输出:
// 1
// 2

提前关闭迭代器

提前关闭迭代器是指在完整迭代可迭代对象之前关闭迭代器。

通过是否实现方法 return() 来决定迭代器是否是可提前关闭的。比如,类型 Array 没有实现方法 return() ,是不可提前关闭的。

提前关闭迭代器会自动调用迭代器中的方法 return() 。

方法 return() 必须返回一个迭代器结果对象,可以只返回 { done: true } 。

提前关闭迭代器的情况:

  • 在循环 for-of 中,通过 break、continue、throw 提前退出循环。
  • 解构操作并未消费所有值。

示例:

// 迭代器类
class Iterator {
	constructor(count, limit) {
		this.count =  count
		this.limit = limit
	}

	next() {
		if(this.count <= this.limit) {
			return { done: false, value: this.count++ }
		} else {
			return { done: true, value: undefined }
		}
	}

	// 定义方法 return
	return() {
		console.log('Exiting early')
		return { done: true }
	}
}

// 可迭代对象类
class Iterable {
	constructor(limit) {
		this.limit = limit
	}
	
	[Symbol.iterator]() {
		let count = 1,
			limit = this.limit
			
		return new Iterator(count, limit)
	}
}

let iterable = new Iterable(2)

for(const item of iterable) {
	if(item > 1){
		break;  // 提前结束迭代
	}
	console.log(item)
}

// 输出:
// 1
// Exiting early

使迭代器成为可迭代对象

一些可迭代对象会使其迭代器成为可迭代对象,并使迭代器的默认迭代器函数返回迭代器自身。(比如,类型 Array ):

// 迭代器类
class Iterator {
	constructor(count, limit) {
		this.count =  count
		this.limit = limit
	}

	next() {
		if(this.count <= this.limit) {
			return { done: false, value: this.count++ }
		} else {
			return { done: true, value: undefined }
		}
	}
	
	// 定义默认迭代器函数
	[Symbol.iterator]() {
		return this
	}
}

// 可迭代对象类
class Iterable {
	constructor(limit) {
		this.limit = limit
	}
	
	[Symbol.iterator]() {
		let count = 1,
			limit = this.limit
			
		return new Iterator(count, limit)
	}
}

// 创建可迭代对象
let iterable = new Iterable(2)
// 创建迭代器
let iter = iterable[Symbol.iterator]()

// 迭代可迭代对象
for(const item of iterable) {
	console.log(item)
}

// 输出:
// 1
// 2

// 迭代迭代器
for(const item of iter) {
	console.log(item)
}

// 输出:
// 1
// 2

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值