影子的知识库

影子的知识库

  • 知识库
  • GitHub

›ES6

JVM系列

  • JVM内存区域
  • 对象创建-布局-访问
  • 内存溢出实战
  • 内存区域回收
  • 四大引用
  • 垃圾回收算法
  • HotSpot回收算法细节

Java系列

  • java注解
  • springboot请求参数绑定
  • springboot请求参数校验框架
  • YAML语法
  • 动态代理
  • classpath和java命令
  • springboot-aop编程
  • springboot统一异常处理
  • springboot数据库和事务
  • springboot拦截器
  • springboot中的web配置
  • docker的简单开发
  • springboot自动配置
  • 数据库的隔离级别
  • springboot监控
  • java类加载
  • java-agent的相关内容
  • 类加载器详解
  • java的SecurityManager
  • maven学习

Node

    JS 基础

    • 语法基础和数据类型
    • 数据类型转换
    • 语句 表达式 运算符
    • 变量与对象
    • 函数
    • 数据处理
    • 常用 API
    • 重点知识

    ES6

    • 块级作用域
    • 字符串和正则表达式
    • 函数
    • 对象
    • Symbol
    • Set和Map
    • 迭代器和生成器
    • 类
    • 数组
    • Promise

    Node 基础

    • 模块系统
    • package.json
    • 内置对象
    • npm脚本的使用
    • Buffer
    • Stream
    • 事件循环机制
    • 示例代码

    stream系列

    • 流的缓冲
    • 可读流
    • 可写流
    • 双工流和转换流
    • 自定义流

后期计划

  • 学习计划
  • 专题研究计划
Edit

本文内容

迭代器

所有迭代器对象都有 next() 方法,每次返回一个结果对象,结果对象有两个属性,一个是 value,表示当前迭代的值,一个是 done,表示迭代是否已经完成。如果我们已经迭代完了最后一个元素,再次访问 next() 时,返回对象的 done 将是 true,value 则包含一个终值,这个值不是我们迭代集合的一部分,而是包含了一些其它信息,常规情况下返回的是 undefined

let m = [2, 3, 5];
let iterator = m[Symbol.iterator]();
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

我们使用 m[Symbol.iterator](); 访问了数组的内建的迭代器。

可以看到 2,3,5 迭代完成后,再次访问 next() 才会返回 done = true,值则是 undefined

一个迭代器是一个只能消费一次的对象,我们使用一个迭代器只能从头到尾迭代一次,迭代器内部保存了迭代的状态,例如当前迭代到第几个元素了,是一种可变对象

生成器

生成器是一种返回迭代器的函数,通过在 function 关键字后面加上星号(*)来表示,在这样的函数中,可以使用 yield 关键字(没有加星号则不能使用这个关键字)

function* generator() {
    console.log(1111);
    yield 1;
    console.log(2222);
    yield 2;
    console.log(3333);
    yield 4;
    console.log(4444);
    return 5;
}

let iterator = generator();
console.log(9999);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: 5, done: true }
console.log(iterator.next()); // { value: undefined, done: true }

输出是

(py3.5) czp@:~/workspace/knowledge-base/demos/node_start$ node hello.js
9999
1111
{ value: 1, done: false }
2222
{ value: 2, done: false }
3333
{ value: 4, done: false }
4444
{ value: 5, done: true }

yield 关键字

使用了 yield 关键字的函数,当我们进行调用时,就会返回一个迭代器对象。这个迭代器对象包含的值就是这个生成器函数体中,yield 的顺序。

例如上例中总共会执行 3 次 yield,分别是 1,2,4,这个生成器返回的迭代器对象就可以依次迭代出这三个值。

迭代器的执行步骤如下:

  • 首先被生成器生成一个迭代器,此时,生成器函数的内部逻辑,一行代码都不执行,表现就是上例中 1111 没有在一开始就输出
  • 迭代器调用 next(),此时,生成器一直执行到第一次 yield 为止,表现就是上例中输出了 1111,没有输出 2222。然后将 yield 的值作为本次返回的对象的 value
  • 迭代器继续调用 next(),此时,生成器从上次 yield 的下一行开始执行(上例中就是 console.log(2222);),一直执行到第二次 yield 为止,然后将 yield 的值作为本次返回的对象的 value
  • 倒数第二次 next(),执行到了 yield 4; 停止,返回的是 4
  • 最后一次 next(),没有碰到 yield,而是碰到了 return,宣告迭代结束,本次迭代返回的是 return 的值(如果没有 return,而是执行完了生成器的方法体,那么返回 undefined),同时 done 的值是 true

可以看出,yield 像是一个一个阶段,每次执行到 yield 就停止,下次从 yield 的下一行开始执行,一直到执行完生成器函数,就代表迭代器迭代结束了。如果我们再次调用 next() 那么依然是返回最后一次的结果。

yield 关键字只能在生成器内部使用,在其它地方使用会报错,即使是在生成器内部的函数里使用也会报错,例如

function* generator(items) {
    items.forEach(function (item) {
        yield item + 1; // 语法错误
    })
}

生成器函数表达式

let generator = function* () {
    console.log(1111);
    yield 1;
    console.log(2222);
    yield 2;
    console.log(3333);
    yield 4;
    console.log(4444);
    return 5;
};

不能用箭头函数来创建生成器

对象属性里创建生成器

let obj = {
    * generator() {
        console.log(1111);
        yield 1;
        console.log(2222);
        yield 2;
        console.log(3333);
        yield 4;
        console.log(4444);
        return 5;
    }
};

这种是 ES 6 里在对象中创建生成器的方式

可迭代对象

可迭代对象都具有 Symbol.iterator 属性,调用该属性方法,可以返回一个作用于该对象的迭代器,例如之前的例子

let m = [2, 3, 5];
let iterator = m[Symbol.iterator]();
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

在 ES 6 中,以下都是可迭代对象

  • 数组
  • Set
  • Map
  • 字符串
  • 通过生成器创建的迭代器(生成器默认会为迭代器的 Symbol.iterator 属性赋值,所以这种迭代器本身是可以迭代的)

for-of 循环

for-of 循环每次执行,都会调用可迭代对象的迭代器的 next() 方法,一直持续到迭代器返回的 done 为 true 为止

let m = [2, 3, 5];
for (let number of m) {
    console.log(number);
}
// 2 3 5

这段代码背后的逻辑

  • JavaScript 引擎调用 m[Symbol.iterator]() 方法生成迭代器
  • 循环体中不断调用迭代器的 next() 方法赋值给 number
  • 当 next() 返回的 done 是 true 时退出循环

Symbol.iterator

上例已经说明了,可迭代对象的 Symbol.iterator 属性是一个方法,调用后可以返回可迭代对象的一个迭代器,通过这个迭代器可以访问可迭代对象的元素

let m = [2, 3, 5];
let iterator = m[Symbol.iterator]();
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

因此我们可以使用这个属性来检测,对象是否是可迭代对象

创建可迭代对象

根据上述说明,我们可以通过给对象添加 Symbol.iterator 属性来创建可迭代对象

let obj = {
    j: 100,
    k: 200,
    l: 300,
    * [Symbol.iterator]() {
        yield this.j;
        yield this.k;
        yield this.l;
    }
};
for (let number of obj) {
    console.log(number);
}
// 100 200 300

内建迭代器

ES 6 很多的可迭代对象都有一些 api 来访问内建的迭代器,不需要访问 Symbol.iterator 这么底层的迭代器

集合对象迭代器

数组、Map、Set 都内建了 3 种迭代器

  • entries() 返回一个迭代器,值为多个键值对
  • values() 返回一个迭代器,值为集合的值
  • keys() 返回一个迭代器,值为键名

entries()

每次调用 next() 返回一个数组,数组 2 个元素分别表示 key 和 value

keys()

每次调用 next() 返回一个 key

values()

每次调用 next() 返回一个 value

字符串迭代器

ES 5 之后,字符串越来越像数组了,例如可以通过方括号访问字符

let str = 'abcd';
console.log(str[2]); // c

对于双编码单元的字符,则会出问题

let str = 'ab😁';
console.log(str[2]); // �

因为这种索引访问的是 UTF-16 编码单元,而不是单个字符。有的字符需要两个 UTF-16 编码单元来表示,因此就出错了

ES 6 添加了字符串迭代器,可以正确的访问字符

let str = 'ab😁';
for (let x of str) {
    console.log(x);
}

输出

(py3.5) czp@:~/workspace/knowledge-base/demos/node_start$ node hello.js
a
b
😁

迭代器参数

我们使用 next() 方法时,还可以给迭代器传递参数,这个参数将会替代迭代器内部上一次 yield 时的返回值。

function* generator() {
    let first = yield 1;
    let second = yield first + 2; // 4 + 2
    yield second + 3; // 5 + 3
}

let iterator = generator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next(4)); // { value: 6, done: false }
console.log(iterator.next(5)); // { value: 8, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

执行步骤如下:

  • 首先生成器生成一个迭代器
  • 迭代器第一次调用 next() 返回的 value 是 1,此时迭代器内部只是执行了 yield 1,但是还没有执行赋值语句 let first = yield 1;
  • 迭代器调用 iterator.next(4),此时迭代器内部的 yield 1 的返回值变成了我们传入的 4,因此 let first = yield 1; 将 first 赋值为 4,于是 yield first + 2 返回的 value 是 6
  • 迭代器调用 iterator.next(4),此时迭代器内部的 yield first + 2 的返回值变成了我们传入的 5,因此 let second = yield first + 2; 将 second 赋值为 5,于是 yield second + 3 返回的 value 是 8
  • 最后一次调用 next() 返回的 value 是 undefined,done 是 true

迭代器的这种行为,可以被用来实现异步编程

在迭代器中抛出错误

我们可以在迭代器中抛出错误,这个错误抛出的地点就是上一次执行 yield 时

function* generator() {
    let first = yield 1;
    try {
        let second = yield first + 2; // 此处将会抛出错误
    } catch (e) {
        console.log(e.message);
        yield 3; // 3
    }
}

let iterator = generator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next(4)); // { value: 6, done: false }
console.log(iterator.throw(new Error('瞬间爆炸'))); // { value: 8, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

throw 也可以迭代一次进行返回

Last updated on 11/8/2020
← Set和Map类 →
  • 迭代器
  • 生成器
    • yield 关键字
    • 生成器函数表达式
    • 对象属性里创建生成器
  • 可迭代对象
    • for-of 循环
    • Symbol.iterator
    • 创建可迭代对象
  • 内建迭代器
  • 集合对象迭代器
    • entries()
    • keys()
    • values()
  • 字符串迭代器
  • 迭代器参数
  • 在迭代器中抛出错误
影子的知识库
Docs
Getting Started (or other categories)Guides (or other categories)API Reference (or other categories)
Community
User ShowcaseStack OverflowProject ChatTwitter
More
BlogGitHub
Copyright © 2020 Cen ZhiPeng