1、前言

JavaScript提供了许多的方法来获取数组或者对象中的某个元素或者属性(迭代)。从以前的for循环到之后的filter、map再到后来的for...in和for...of的迭代机制。只要具有iterator接口的都可被迭代。

2、迭代器 Iterator

2.1 含义

迭代器(iterator)为各种数据结构,提供一个统一的、简便的访问接口,简单的说,迭代可以是数组或对象的遍历方式。它使得数据结构的成员能够按某种次序排列,如上面所说,只要部署 Iterator 接口,就可以完成遍历操作。即依次处理该数据结构的所有成员。

2.2 工作过程

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

2.3 Symbol.iterator属性

2.3.1 简介

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性。也就是说,只要某种数据结构具有iterator接口,就是可遍历的。常见的具有这一特点的数据结构有:Array、Map、Set、String、TypedArray(类型化的数组)、函数的 arguments 对象、NodeList 对象

2.3.2 示例1:数组的iterator示例

let arr = [1, 2, 3]
let iter = arr[Symbol.iterator]()
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

运行结果:
{ value: 1, done: false }
{ value: 2, done: false }

{ value: 3, done: false }

{ value: undefined, done: true }

其中,上面的iter返回的是一个迭代器对象,由于arr数组只有3个元素,所以经过3次的next()调用之后,第四次指针指到尾部了,已经结束了,所以获取的value值为undefined。迭代已完成,所以返回done。

2.3.3 对象的Iterator

一个对象如果要具备可被for...of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。

2.3.4 示例2 实现iterator接口的自定义类示例

class RangeIterator {
constructor(start, stop, step) {
this.value = start;
this.stop = stop;
this.step = step;
}
[Symbol.iterator]() {
return this;
}
next() {
let value = this.value;
if (value < this.stop) {
this.value += this.step;
return { done: false, value: value };
}
return { done: true, value: undefined };
}
}
function range(start, stop, step = 1) {
return new RangeIterator(start, stop, step);
}
for (let value of range(0, 9, 2)) {
console.log(value);
}

输出结果:
0
2
4
6
8

2.3.5 示例3 为对象添加iterator示例

// 给对象添加iterator接口
let obj = {
data: [1, 2, 3, 4, 5, 6],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
}
}
let sum = 0
for (let d of obj) {
sum += d
}
console.log(sum);

输出结果:
21

3、生成器 Generator

3.1、含义

前面的文章 中也提到过生成器,举个最简单的例子:

let gen = function* () {
yield 1;
yield 2;
yield 3;
}
for (let n of gen()) {
console.log(n);
}

很明显,生成器的函数的function后面有个*,函数中存在yield 关键字,在函数中,通过gen()进行函数调用并生成控制器,在这里是通过循环执行函数的。

再举个例子

let obj = {
name: "Tom",
age: 20,
*[Symbol.iterator]() {
yield this.name;
yield this.age;
}
}
console.log(obj[Symbol.iterator]().next());
console.log([...obj]);
for (let k of obj) {
console.log(k);
}

输出结果:
{ value: 'Tom', done: false }
[ 'Tom', 20 ]
Tom
20

可以看到,Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

3.2 工作过程

遍历器对象的next方法的运行逻辑:

  • 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
  • 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
  • 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
  • 如果该函数没有return语句,则返回的对象的value属性值为undefined。

3.3 Generator 函数的return方法

举个例子:

// 生成器函数
let gen = function* () {
yield 1;
yield 2;
yield 3;
return 'ok'
}
let g = gen()
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());

输出结果:
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 'ok', done: true }
{ value: undefined, done: true }

由于在遍历到第三个next()时循环就已经结束了。再次遍历返回OK,done为true,再次遍历,由于指针已经到队列末尾,所以值为undefined。

如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。

3.4 示例:定义类Fib,实现Fibonacci数列的获取

Fibonacci数列的后一个数等于前两个数之和

// 斐波拉契数列
class Fib {
constructor(num) {
this.num = num
}
*[Symbol.iterator]() {
let [a, b] = [0, 1]
while (true) {
[a, b] = [b, a + b]
if (a > this.num) return
yield a
}
}
}
let fibs = new Fib(100)

for (let f of fibs) {

console.log(f);

}

输出结果:
1
1
2
3
5
8
13
21
34
55
89

最新文章

  1. (临时)C#中,exe 单例运行
  2. vim入门过程
  3. mysql通过data文件恢复数据库的方式
  4. ASP.NET将原始图片按照指定尺寸等比例缩放显示图片
  5. 如何使用UltraCompare对比两个文件夹内容差异
  6. AccessRandomFile多线程下载文件
  7. 2016年3月AV评测
  8. Zendframework application 引导过程
  9. 1494. Monobilliards(栈)
  10. There is no ID/IDREF binding for IDREF
  11. Linux企业级开发技术(6)——libevent企业级开发之内存管理
  12. Eclipse的Tomcat热部署,免重启的方法
  13. CentOS6.5下Mysql数据库的安装与配置
  14. Oracle EBS-SQL (SYS-6):sys_在线用户职责查询.sql
  15. NSURLRequest POST方式请求服务器示例
  16. 关于IMP由拥有DBA权限的用户EXP数据时,数据存放表空间的几种情况(IMP-00013) -1
  17. oracle 中如何查看某个表所涉及的存储过程
  18. margin和padding的用法与区别--以及bug处理方式
  19. Maven的依赖管理
  20. Ubuntu 14 安装Java(JRE、JDK)、Maven

热门文章

  1. 调用 StatefulWidget 组件的参数时(widget.xxx)报 Invalid Constant Value
  2. R型医用变压器为什么越来越受大众喜爱?
  3. 你言我语 By Twikoo
  4. Ubuntu20.04配置 ES7.17.0集群
  5. POJ3761 Bubble Sort (组合数学,构造)
  6. 简单创建一个SpringCloud2021.0.3项目(一)
  7. 【题解笔记】PTA基础6-10:阶乘计算升级版
  8. Job And Schedule (V8R6C3)
  9. kingbaseES R3 集群配置 SSL
  10. Spring_事务总结