千哥读书笔记:JavaScrip迭代器原理

迭代器是Javascript经常用到,但却又非常难以理解的内容,不论是《JavaScript权威指南》还是《JavaScript高级程序设计》等经典学习资料,都没有透彻地讲明白,千哥最近查阅了大量的资料,终于搞清楚一些核心原理,这里还是以笔记的形式进行体现。

一、迭代及相关概念

迭代的英文“iteration”意思是“重复”或“再来”,在软件开发领域,“迭代”的意思是按照顺序反复多次执行一段程序,通常会有明确的终止条件。例如,最常见的以for为代表的计数循环:

let iterable = [99,88,77];

for(let index = 0; index < iterable.length; index++){
    console.log(iterable[index]);//99 88 77
}

在这段代码里,定义了一个数组interable,然后利用这个数组的length属性和每次递增的index,用for实现了对interable的元素输出。当然,更常见的迭代模式是通过for/of进行迭代:

let iterable = [99,88,77];
for(let item of iterable){
    console.log(item);//99 88 77
}

关于迭代,首先要搞清楚一些基本概念:

1、可迭代对象(iterable):指实现了Iterable接口(可迭代协议)的对象,包括常见的数组、字符串、set对象和Map对象等原生可迭代对象,也包括其他自定义可迭代对象。这些对象具有专用的“迭代器方法”,且该方法返回“迭代器对象”。

2、迭代器方法:使用了符号Symbol.iterator作为键的方法:[Symbol.iterator]()。这种方法供“可迭代对象”使用,是可迭代对象的标志。

3、迭代器对象(iterator):具有next()方法的对象,且该方法返回“迭代结果对象”。

4、迭代器结果对象:具有value和done属性的对象,是“迭代器对象”返回的结果。

需要注意的是:

1、在ECMAScript的语境下,一些数据结构实现了正式的Iterable接口(可迭代协议),而且可以通过迭代器Iterator消费,这些结构则称为“可迭代对象”。

2、实现Iterable接口(可迭代协议)的数据结构要求同时具备两种能力:支持迭代的自我识别能力和创建实现Iterator接口对象的能力。这意味着必须暴露一个属性作为“默认迭代器”。

3、作为“默认迭代器”的属性必须使用特殊的Symbol.iterator作为键,形成一个工厂函数[Symbol.iterator](),即“迭代器方法”,这个方法必须返回一个新的迭代器,即“迭代器对象”。

4、迭代器方法[Symbol.iterator](),其中的[...]语法被称为“计算属性名”,这在对象术语定义中是指,指定一个表达式并用这个表达式的结果作为属性的名称,Symbol.iterator是ES6预定义的特殊Symbol类型值之一。

5、可以把可迭代对象理解成数组或集合这样的集合类型的对象,它们包含的元素都是有限的,而且都具有无歧义的遍历顺序。

6、可迭代对象不一定是数组、字符串、set对象和Map对象,也可以是其他自定义的、具有类似上述对象的行为的其他数据结构。

7、迭代器对象(iterator)是按需创建的一次性对象,每个迭代器都会关联一个可迭代对象(iterable)。

二、迭代的基本原理

在上面的例子中,数组iterable就属于可迭代对象,对于for/of循环来说,数组iterable和其他可迭代对象有内置的、专用的迭代器方法,也就是[Symbol.iterator]() 。

根据EMCAScript规范,Symbol.iterator这个符号作为可迭代对象的一个属性,表示“一个方法,该方法返回对象默认的迭代器,由for/of语句使用”。

换句话说,正是因为数组iterable和其他可迭代对象有这个专用的迭代器方法[Symbol.iterator]() ,这些对象才能被for/of语句进行迭代。

进一步说,要实现数组iterable的迭代,也可以显式地调用[Symbol.iterator]() 这个方法:

let iterable = [99,88,77];
let iterator = iterable[Symbol.iterator]();
while(true){
    let result = iterator.next();
    console.log(result.value);//99 88 77 undefined
    console.log(result.done);//false false false true
    if(result.done) break;
}

在这个例子中,数组iterable显式地调用了[Symbol.iterator]()方法,返回了一个迭代器对象iterator,并在while循环中反复调用iterator的next()方法。由迭代器对象iterator的next()方法,生成了迭代器结果对象result,然后利用result的两个内置的属性done和value,对数组iterable的元素进行了输出。

迭代器结果对象result的两个属性done和value,在迭代结束之前,done会返回布尔值fase,value会返回当前值(比如99);在迭代结束之后,done会返回布尔值true ,value会返回undefined。

而对于其他自定义的对象来说,只要添加了[Symbol.iterator]()方法,并且通过这个方法可以生成迭代器对象(具有next()方法的对象),那么这个对象就会成为可迭代对象,就可以被for/of等语句进行迭代操作。

对于上述迭代的基本原理,可以用以下示意图表示:

需要注意的是:

1、原生可迭代对象(包括数组、字符串、set对象和Map等对象)已经内置了[Symbol.iterator]()这个迭代器方法,而这些对象与其他自定义的、已实现可迭代协议(Iterable接口)的所有类型,都会自动兼容收接可迭代对象的任何语言特性。

2、符合“接收可迭代对象的原生语言特性”的操作包括:

  • for-of循环
  • 数组解构
  • 扩展操作符(...)
  • Array.from()
  • 创建集合
  • 创建映射
  • Promise.all()接收由期约组成的可迭代对象
  • Promise.race()接收由期约组成的可迭代对象
  • yield*操作符,在生成器中使用

这些原生语言结构会在后台调用向其提供的可迭代对象的[Symbol.iterator]()这个工厂函数,从而创建一个迭代器。也就是说,在迭代过程中,显式调用[Symbol.iterator](),并不是必需的,

3、例如在fo/of语句中,fo/of具有接收可迭代对象的原生语言特性,fo/of首先会在后台判断接收的对象是否为可迭代对象,判断的必要条件,就是看这个对象是否具有[Symbol.iterator]()方法,如果没有,就不是可迭代对象,就会报错。

换句话说,参与迭代的对象,哪怕是自定义的对象,只要有[Symbol.iterator]()方法,那么这个循环就可以进行下去。如下面的例子:

let iterable = [3, 5, 7];
for(let item of iterable){
    console.log(item);//3,5,7
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值