Javascript高级程序设计第七章 | ch7 | 阅读笔记

迭代器与生成器

在软件开发领域,”迭代“的意思是按照顺序反复多次执行一段程序

理解迭代

在JavaScript中,计数循环就是最简单的迭代
但是这种迭代有点问题:
1. 迭代之前需要事先知道使用何种数据结构
2. 遍历顺序并不是数据结构固有的
后面js实现了Array.prototype.forEach()向通用迭代迈出了一步
ES6实现了迭代器模式

迭代器模式

迭代器模式描述了一个方案,即把有些结构称为可迭代对象,因为它们实现了正式的Iterable接口,并且可以通过迭代器Iterator消费

可迭代对象不一定非得是集合对象,也可以是有类似数组行为的其他数据结构

每一个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联可迭代对象的API。迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。

可迭代协议

实现Iterator接口(可迭代协议)需要同时具备两种能力:

  1. 支持迭代的自我识别能力
  2. 创建实现Iterator接口的对象的能力
    这意味着必须暴露一个属性作为默认迭代器,而且这个属性必须必须使用特殊的Symbol.iterator作为键。

我的理解:
上述提到实现了Iterable接口的对象称为可迭代对象,实际上可迭代对象实现的属性必须用Symbol.iterator作为属性,属性值是一个工厂函数,调用这个工厂函数,会返回一个新迭代器。

// 给Number实现的Iterator
let agg = 10;
Number.prototype[Symbol.iterator] = function() {
    return {
        next: function() {
            return 'azoux iteration test'
        }
    }
}
ƒ () {
    return {
        next: function() {
            return 'azoux iteration test'
        }
    }
}
agg[Symbol.iterator]()
{next: ƒ}

在实际写代码中不用显式地调用这个工厂函数来生成迭代器。实现可迭代协议的所有类型都会自动兼容接收可迭代对象的任何语言特性


// 特性:
// for of

// Promise.all

// Promise.race()

//yield

let arr = [1, 2, 3];

// 数组解构

let [a, b, c] = [1, 2, 3];

// 扩展运算符

let arr2 = [...arr];

// Array.from(arr)

// Set构造函数

// Map构造函数

迭代器协议

迭代器API使用next()方法在可迭代对象中遍历数据,每次成功调用都会返回一个IteratorResult对象

自定义迭代器

class Counter {

 constructor(limit) {

 this.count = 1;

 this.limit = limit;

 }

  

 next() {

 return this.count <= this.limit ? {

 done: false,

 value: this.count++

 } : {

 done: true,

 value: undefined

 };

 }

  

 [Symbol.iterator]() {

 return this;

 }

}

  

const counter = new Counter(3);

const it = counter[Symbol.iterator]();

console.log(it.next()); // { done: false, value: 1 }

  

for (let i of counter) console.log(i); // 2, 3

上面的类实现了Iterator接口,但是不太理想,因为它的每个实例都只能被迭代一次。
我们想要一个可迭代对象可以多次使用,因此可以考虑使用闭包,把计数器变量放到闭包中。

/**

 * 自定义迭代器

 */

class Counter {

 constructor(limit) {

 this.limit = limit;

 }

  

 [Symbol.iterator]() {

 let limit = this.limit;

 let count = 1;

 return {

 next() {

 return count <= limit ? {

 done: false,

 value: count++

 } : {

 done: true,

 value: undefined

 };

 }

 }

 }

}

const counter = new Counter(3);

  

for (let i of counter) console.log(i); // 1, 2, 3

console.log('span'); // 

for (let i of counter) console.log(i); // 1, 2, 3

提前终止迭代器

因为return()方法是可选的,所以并非所有迭代器都是可关闭的。
仅仅给一个不可关闭的迭代器加上return()方法并不能让它变成可关闭的
调用return()不会强制迭代器进入关闭状态
即便如此,return()方法还是会被调用

/**

 * 自定义迭代器

 */

class Counter {

 constructor(limit) {

 this.limit = limit;

 }

  

 [Symbol.iterator]() {

 let limit = this.limit;

 let count = 1;

 return {

 next() {

 return count <= limit ? {

 done: false,

 value: count++

 } : {

 done: true,

 value: undefined

 };

 },

 return () {

 console.log('Exiting early!');

 return {

 done: true

 };

 }

 }

 }

}

  

let counter1 = new Counter(5);

  

for (let i of counter1) {

 if (i > 2) break;

 console.log(i);

}

// 1

// 2

// Exiting early!

  

let counter2 = new Counter(5);

try {

 for (let i of counter2) {

 if (i > 2) throw 'err';

 console.log(i);

 }

} catch (e) {

 console.log(e);

}

// 1

// 2

// Exiting early!

// err


let arr = [1, 2, 3, 4, 5, 6, 7];

for (let i of arr) {

 if (i > 2) break; // 1, 2

 console.log(i);

}

console.log('---------------------'); // 1 2 3 4 5 6 7

for (let i of arr) console.log(i);

生成器

es6拥有在函数块内暂停和恢复代码执行的能力

生成器基础

  1. 生成器的形式是一个函数,函数名称前加上*号,来表示它是一个生成器。只要是能定义函数的地方都能定义生成器
  2. next方法的返回值类似迭代器,valued值默认为undefined,可以通过迭代函数返回值指定
  3. 生成器函数只会在初次调用next()方法后开始执行
/**

 * 生成器定义

 */

let gen1 = function* () {

  

}

  

function* gen2 () {

  

}

// and so on....
function* genFunc() {

 console.log('gen test');

 return 'azoux';

}

  

let gen = genFunc();

console.log(gen); // genFunc {<suspended>}

console.log(gen.next()); // gen test   {value: 'azoux', done: true}

console.log(gen); // genFunc {<closed>}

console.log(gen[Symbol.iterator]); // [Symbol.iterator]() { [native code] }

console.log(gen[Symbol.iterator]()); // genFunc {<closed>}

通过yield中断

  1. yield可以让生成器停止和开始执行
  2. 生成器函数在遇到yield之前会正常执行
  3. 遇到yield后会暂停执行,函数作用域的状态被保留,停止执行的生成器函数只能通过在生成器函数上调用next()方法来恢复执行
 * yield不能嵌套使用。只能出现在生成器函数内部使用

 */

function* genFunc() {

 yield 'azoux';

 yield 'domy';

 return 'the end';

}

const gen = genFunc();

console.log(gen.next()); // {value: 'azoux', done: false}

console.log(gen.next()); // {value: 'domy', done: false}

console.log(gen.next()); // {value: 'the end', done: true}

console.log(gen.next()); // {value: undefined, done: true}

  

/**

 * 生成器对象作为可迭代对象

 */

for (const c of genFunc()) console.log(c);

// azoux

// domy
/**

 * 使用yield实现输入输出

 * yield关键之会接收传给next()方法的第一个值

 * 第一次调用next()传入的值不会被使用,因为这一次调用是为了开始执行生成器函数

 */

function* genFunc(initial) {

 console.log(initial);

 console.log(yield);

 console.log(yield);

}

  

let gen = genFunc('step 0'); // step 0

gen.next('step 1');

gen.next('step 2');

gen.next('step 3');

function* nTimes(n) {

 for (let i = 0; i < n; i += 1) {

 yield i;

 }

}

  

for (let x of nTimes(5)) console.log(x);
/**

 * 产生可迭代对象

 * 用 * 增强yield的行为

 * yield* 实际上就是把一个可迭代对象序列化为一连串可以单独产出的值

 * yield* 的值是关联迭代器返回done: true时的value属性值

 * 对于生成器函数的迭代器来说,这个值就是生成器函数自己return的值

 */

  

function* genFunc() {

 yield*[1, 2, 3];

}

  

for (let k of genFunc())

 console.log(k); // 1 2 3

  

function* innerGen() {

 yield 'step 1';

 yield 'step 2';

 return 'step 3';

}

  

function* outerGen() {

 console.log('iter value: ', yield* innerGen());

}

  

for (let k of outerGen()) {

 console.log('value: ' + k);

}

// value: step 1

// value: step 2

// iter value:  step 3
/**

 * yield* 实现递归

 */

function* nTimes(n) {

 if (n > 0) {

 yield* nTimes(n - 1);

 yield n - 1;

 }

}

  

for (const c of nTimes(3)) console.log(c);

生成器作为默认迭代器

提前终止生成器

  1. return()
  2. throw()
posted @ 2022-01-25 16:43  azoux  阅读(38)  评论(0编辑  收藏  举报