Iterator 、Generator

爱是天时地利的迷信---《原来你也在这里》

宝宝们,周末过得充实有趣吗?今天上午下雨了,时小时大,雨声很好听~

speak is cheap ~

一、Iterator

Iterator(遍历器)的概念

JS里原有的表示”集合“的数据结构,主要是Array和Object,ES6又添加了Map和Set。我们可以任意组合和设计数据的结构,那么就需要一个机制,可处理所有不同的数据结构。

Iterator就是这个用途,他是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据只要不输Iterator接口,就可以完成遍历操作(依次处理该数据结构的所有成员)。

Iterator作用

  • 为各种数据结构提供统一的、简便的访问接口
  • 使得数据结构的成员按照某种次序排列
  • ES6创造了新的遍历命令for...of循环,Iterator接口主要供for...of消费

Iterator的遍历过程

  1. 创建一个指针对象,指向当前数据结构的起始位置。遍历器对象本质就是一个指针对象(遍历器对象)
  2. 第一次调用指针对象的next方法,可以将 数据结构的第一个成员
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置

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

Iterator

Iterator接口的目的,就是为所有数据结构提供一种统一的访问机制。即for...of循环。当使用for...of循环遍历某种数据结构时,该循环会自动寻找Iterator接口。

数据结构只要部署了Iterator接口,我们就称这种数据结构是”可遍历的“

ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性,Symbol.iterator属性本身是一个函数,是当前数据结构默认的遍历器生成函数。执行这个函数就返回一个遍历器。

上面的话头大吗?还是看代码吧!

const obj = {
  [Symbol.iterator] : function () {
    return {
      next: function () {
        return {
          value: 1,
          done: true
        };
      }
    };
  }
};

ES6的某些数据结构原生部署了Symbol.iteraotr属性,即不用任何处理就可以被for...of循环遍历。另外一些数据结构没有。

原生具备Iterator接口的数据结构:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的arguments对象
  • NodeList对象
let arr = ['a', 'b', 'c'];
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: undefined, done: true }

调用Iterator接口的场合

有些场合会默认调用Iterator接口,如下:

1.解构赋值

对数组和Set结构解构赋值

let set = new Set().add('a').add('b').add('c');

let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b','c'];

2.扩展运算符

扩展用算符(...)也会调用默认的Iterator接口

// 例一
var str = 'hello';
[...str] //  ['h','e','l','l','o']

// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']

3.yield*

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

let generator = function* () {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

var 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 }

4.其他场合

任何接受数组最为参数的场合,其实都调用了遍历器接口:

  • for...of
  • Array.form()
  • Map(),Set(),WeakMap(),WeakSet()
  • Promise.all()
  • Promise.race()

Iterator 接口最简单的实现

Symbol.iterator方法最简单实现,还是使用下一章介绍的Generator函数

let myIterable ={
  [Symbol.iterator]:function*(){
    yield 1;
    yield 2;
    yield 3;
  }
}
[...myIterable] //[1,2,3]
//更简洁写法
let obj = {
  *[Symbol.iterator](){
    yield 'hello';
    yield 'world';
  }
}
for(let x of obj){
  console.log(x)
}
// 'hello'
// 'world'

与其他遍历语法的比较

  • for循环: 写法比较麻烦
  • forEach 写法简单,但是无法跳出forEach循环,break和return都不奏效
  • for...in 遍历数组的键名。缺点 - 数组键名是数字,但是for...in循环是以字符串作为键名”0“,”1“,”2“等 - 不仅遍历数字键名,还会遍历手动添加的其他键,设置包括原型上的键 - 某些情况下,for...in循环会以任意顺序遍历键名
  • for...of循环优点 -语法简洁 -可以与break、continue、return配合使用 - 可遍历所有数据结构

二、Generotor

Generator的概念

Generator函数是ES6提供的一种异步编程解决方案,语法行为和传统函数完全不同。

在语法上,Generator函数是一个状态机,封装了多个内部状态。

执行Generator函数会返回一个遍历器对象,也就是说,他还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。

形式上,Generator函数是一个普通函数,有两个特征:

  1. function和函数名之间有一个星号
  2. 函数体内部使用yield(产出)表达式,定义不同的内部状态
function* helloWorldGenerator(){
  yield 'hello';
  yield 'world';
  return 'ending';
}
var hw = helloWorldGenerator();

Generator函数调用与普通函数一样,但是,调用Generator函数后,该函数并不执行。返回的也不是函数运行结果,而是一个指向内部状态的指针对象(遍历器对象,Iterator对象);

必须调用遍历器对象的next方法,使得指针移向下一个状态。每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。即,yield是分段执行的,yield表达式是暂停执行的标志,而next方法可以恢复执行。

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

未完待续……

愿我们有能力不向生活缴械投降---Lin

本文分享自微信公众号 - 女程序员的日常(gh_df41d619fb70)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 字节序: 一个不是很重要的概念

    我们将一个4字节的汉字存入一段4字节的物理容器里, 该怎么存放? 直觉都是从左往右依次写入, 但也可以从右向左写, 甚至可以先写入奇字节再写偶字节, 这样比划下...

    Jean
  • 程序员必读经典长文:用十年时间自学编程

    AI开发者按:相信很多做技术的同学都自学过,也看过「Teach Yourself Programming in Ten Years」这篇文章。虽然离初次发表已经...

    AI研习社
  • LightOJ - 1214 Large Division 大数取余

    用户2965768
  • Java的ThreadLocal

    ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状...

    用户3467126
  • shiro 加密登录 密码加盐处理

    一觉睡到小时候
  • 轻松应对Java试题,这是一份大数据分析工程师面试指南

    导语:经过这一段时间与读者的互动与沟通,本文作者发现很多小伙伴会咨询面试相关的问题,特别是即将毕业的小伙伴,所以决定输出一系列面试相关的文章。本文Java篇,介...

    AI科技大本营
  • Spring Boot(十二):Spring Boot 如何测试打包部署

    有很多网友会时不时的问我, Spring Boot 项目如何测试,如何部署,在生产中有什么好的部署方案吗?这篇文章就来介绍一下 Spring Boot 如何开发...

    纯洁的微笑
  • Java-No enclosing instance of type TestExtends is accessible.的错误纠正

    这个错误是我在进行Java继承学习时候遇到的,但是此错误和继承并没有关系。这里Run一下会出现错误,No enclosing instance of type ...

    Fisherman渔夫
  • 深入理解Javacript从作用域作用域链开始

    作用域是你的代码在运行时,某些特定部分中的变量,函数和对象的可访问性。换句话说,作用域决定了变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

    coder_koala
  • 经常被面试官考的JavaScript数据类型知识你真的懂吗?

    面试了几个开发者,他们确实做过不少项目,能力也是不错的,但是发现javascript基础并不好,于是决定写一下这篇javascrip数据类型相关的基础文章,其实...

    coder_koala

扫码关注云+社区

领取腾讯云代金券