资讯专栏INFORMATION COLUMN

ES6之Iterator、Generator

ashe / 1458人阅读

摘要:举个例子遍历器生成函数,作用就是返回一个遍历器对象,方法返回一个对象,表示当前数据成员的信息。该对象本身也具有属性,执行后返回自身。

Iterator的作用

一是为各种数据结构,提供一个统一的、简便的访问接口;(统一)
二是使得数据结构的成员能够按某种次序排列;(按序
三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
举个例子:遍历器生成函数,作用就是返回一个遍历器对象,next方法返回一个对象,表示当前数据成员的信息。这个对象具有value和done两个属性,value属性返回当前位置的成员,done属性是一个布尔值,表示遍历是否结束

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}

对于遍历器对象来说,done: false和value: undefined属性都是可以省略的,所以上述代码可以简写为:

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++]} :
        {done: true};
    }
  };
}

Iterator 只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构

Iterator接口

默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器
原生具备 Iterator 接口的数据结构如下:
1)Array
2)Map
3)Set
4)String
5)TypedArray
6)函数的 arguments 对象
对于部署了Iterator接口的数据结构除了可以使用for of循环之外,可以用while判断对象的done属性进行循环遍历
怎么样使用原生的遍历器呢?

let arr = ["a", "b", "c"];
let iter = arr[Symbol.iterator]();

iter.next()

//第二个例子:
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"

var iterator = someString[Symbol.iterator]();

iterator.next()  // { value: "h", done: false }

对于类似数组的对象应该怎样调用数组的Symbol.iterator方法?

let iterable = {
  0: "a",
  1: "b",
  2: "c",
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]//这句话是重点
};
for (let item of iterable) {
  console.log(item); // "a", "b", "c"
}
使用Iterator的场景

解构赋值

let set = new Set().add("a").add("b").add("c");
let [first, ...rest] = set;
// first="a"; rest=["b","c"];
//对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法

扩展运算符

var str = "hello";
[...str] //  ["h","e","l","l","o"]
//扩展运算符(...)也会调用默认的 Iterator 接口

yield*

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

var iterator = generator();

iterator.next()
遍历器的return和throw方法

遍历器对象生成函数,next方法是必须部署的,return方法和throw方法是否部署是可选的。return方法必须返回一个对象

function readLinesSync(file) {
  return {
    next() {
      if (file.isAtEndOfFile()) {
        file.close();
        return { done: true };
      }
    },
    return() {
      file.close();
      return { done: true };
    },
  };
}
for (let line of readLinesSync(fileName)) {
  console.log(line);
  break;//我们让文件的遍历提前返回,这样就会触发执行return方法
}
for of 循环优点

forEach没办法跳出循环,也就是说在forEach当中break命令或return命令都不能奏效
for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键,而且for in 是没有顺序的(也就是说for...in循环主要是为遍历对象而设计的,不适用于遍历数组)

小诀窍:
1、并不是所有类似数组的对象都具有 Iterator 接口,一个简便的解决方法,就是使用Array.from方法将其转为数组

let arrayLike = { length: 2, 0: "a", 1: "b" };

// 报错
for (let x of arrayLike) {
  console.log(x);
}

// 正确
for (let x of Array.from(arrayLike)) {
  console.log(x);
}

2、因为普通对象并没有部署Iterator接口,所以是无法使用for of循环的,用以下两种方案解决:

for (var key of Object.keys(someObject)) {
  console.log(key + ": " + someObject[key]);
}

//第二种方式:
function* entries(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}
for (let [key, value] of entries(obj)) {
  console.log(key, "->", value);
}
//第三种方式:
function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj);
  for (let propKey of propKeys) {
    yield [propKey, obj[propKey]];
  }
}
for (let [key, value] of objectEntries(obj)) {
  console.log(`${key}: ${value}`);
}
//或者也可以这样
let jane = { first: "Jane", last: "Doe" };

jane[Symbol.iterator] = objectEntries;//这句话是重点将 Generator 函数加到对象的Symbol.iterator属性上面

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`);
}
Generator简介

在我看来Generator就是为了异步编程提供一种解决方案;
Generator 函数是一个普通函数,事实上它是遍历器生成函数,但有几个特性:
1、function关键字与函数名之间有一个星号(星号紧跟在function关键字后面);
2、函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出” 1)yield只能在Generator函数中使用;2)yield表达式如果用在另一个表达式之中,必须放在圆括号里面;3)yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号);
3、可以把Generator函数理解为状态机,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止;Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable]

Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。

function* gen(){
  // some code
}

var g = gen();

g[Symbol.iterator]() === g
Generator中next方法的参数

通过next方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值
由于next方法的参数表示上一个yield表达式的返回值,所以第一次使用next方法时,不能带有参数。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的
举个简单的例子吧:

function* dataConsumer() {
  console.log("Started");
  console.log(`1. ${yield}`);
  console.log(`2. ${yield}`);
  return "result";
}

let genObj = dataConsumer();
genObj.next();
// Started
genObj.next("a")
// 1. a
genObj.next("b")
// 2. b

复杂一点的:

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
//解释一下结果y=2*12 所以返回的是24/3
b.next(13) // { value:42, done:true }
//y=24 z=13 所以返回的是5+24+13
说说for of与Generator的关系吧

有一点需要注意的是当next方法中返回done为true则终止循环且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中

function *foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
Generator中的throw方法

Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获,throw方法可以接受一个参数,该参数会被catch语句接收

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log(e);
  }
};

var i = g();
i.next();
i.throw(new Error("出错了!"));
// Error: 出错了!(…)

throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。

var gen = function* gen(){
  try {
    yield console.log("a");
  } catch (e) {
    // ...
  }
  yield console.log("b");
  yield console.log("c");
}

var g = gen();
g.next() // a
g.throw() // b
g.next() // c

Generator中的throw有什么优势呢?多个yield表达式,可以只用一个try...catch代码块来捕获错误,大大方便了对错误的处理。

一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了

function* g() {
  yield 1;
  console.log("throwing an exception");
  throw new Error("generator broke!");
  yield 2;
  yield 3;
}

如上所示代码:因为抛出了异常generator broke,所以后面的2 3都不会返回

Generator return
function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next()        // { value: 1, done: false }
g.return("foo") // { value: "foo", done: true }
g.next()        // { value: undefined, done: true }

g调用return方法后,返回值的value属性就是return方法的参数foo。并且,Generator函数的遍历就终止了,返回值的done属性为true,以后再调用next方法,done属性总是返回true

另外一个特殊情况: Generator 函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行。也就是说:调用return方法后,就开始执行finally代码块,然后等到finally代码块执行完,再执行return方法

function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
yield*表达式

yield表达式后面跟的是一个遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为yield*表达式

let delegatedIterator = (function* () {
  yield "Hello!";
  yield "Bye!";
}());

let delegatingIterator = (function* () {
  yield "Greetings!";
  yield* delegatedIterator;
  yield "Ok, bye.";
}());

for(let value of delegatingIterator) {
  console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye."
Generator函数中的this

Generator函数不能跟new命令一起用
怎样让Generator 函数返回一个正常的对象实例,既可以用next方法,又可以获得正常的this?

function* F() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}
//这是第一种方式
var obj = {};
var f = F.call(obj);//让F内部的this对象绑定obj对象

//第二种方式
var f = F.call(F.prototype);

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}

obj.a // 1
obj.b // 2
obj.c // 3

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/112241.html

相关文章

  • ES6 Iterator&Generator

    摘要:可迭代对象就具有属性,它是一种与迭代器密切相关的对象。它通过指定的函数可以返回一个作用于附属对象的迭代器。迭代器特点每次调用方法时,返回一个数组,数组中两个元素,分别表示键和值。示例之输出输出输出之迭代器特点返回集合中存在的每一个键。 Iterator由来 不推荐Iterator方法。 Iterator 函数是一个 SpiderMonkey 专有特性,并且会在某一时刻被删除。有一点,需...

    xietao3 评论0 收藏0
  • ES6IteratorGenerator

    摘要:举个例子遍历器生成函数,作用就是返回一个遍历器对象,方法返回一个对象,表示当前数据成员的信息。该对象本身也具有属性,执行后返回自身。 Iterator的作用 一是为各种数据结构,提供一个统一的、简便的访问接口;(统一)二是使得数据结构的成员能够按某种次序排列;(按序)三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。举个例子:遍历器生...

    tuomao 评论0 收藏0
  • es6 generator函数

    摘要:返回的遍历器对象可以依次遍历函数内部的每一个状态。示例内部捕获外部捕获内部捕获外部捕获上面代码遍历器对象连续抛出两个错误,第一个被函数体内的捕获。上面代码中,首先执行函数,获取遍历器对象,然后使用方法第二行,执行异步任务的第一阶段。 参考 来源《ecmascript6 入门》generator部分 认识generator函数 形式上,generator函数有两个特点:一是functio...

    voidking 评论0 收藏0
  • ES6系列---迭代器(Iterator)与生成器(Generator

    摘要:迭代器的出现旨在消除这种复杂性并减少循环中的错误。返回一个迭代器,其值为集合的值。在迭代器中抛出错误除了给迭代器传递数据外,还可以给它传递错误条件。通过方法,当迭代器恢复执行时可令其抛出一个错误。 循环语句的问题 var colors = [red, green, blue]; for(var i=0; i= items.length); var value =...

    DrizzleX 评论0 收藏0

发表评论

0条评论

ashe

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<