一、先来看看迭代器的例子
class Counter {
constructor(limit) {
this.limit = limit;
}
[Symbol.iterator]() {
let count = 1, limit = this.limit;
return {
next() {
if (count <= limit) {
return { done: false, value: count++ };
} else {
return { done: true, value: undefined };
}
},
return(){
console.log("提前退出");
return {done:true};
}
}
}
}
let counter = new Counter(3);
for (let i of counter) {
console.log(i);
if(i==2){
break; // 1 // 2 // 提前退出
}
}
java中定义迭代器,一般是通过实现迭代器接口,重写next方法,在js中无需实现接口,可迭代对象的实现直接是通过定义[Symbol.iterator]方法实现。
看下Chat gpt对上述的描述:
JavaScript 中的迭代器(Iterator)协议并不是通过传统的接口(interface)来定义,而是通过约定一个具有特定名称的方法,即 [Symbol.iterator] 方法。这样的设计主要是为了保持语言的灵活性和简洁性。
在 JavaScript 中,对象可以被视为拥有一组特定行为(方法)的集合,而不必明确实现某个接口。这种约定式的设计允许开发者自由地定义对象的行为,而不受特定接口的限制。
关于 [Symbol.iterator] 方法的作用:
当一个对象被用于 for...of 循环时,或者使用 ... 扩展运算符时,JavaScript 将会调用该对象的 [Symbol.iterator] 方法,获取一个迭代器对象。
迭代器对象需要包含一个 next() 方法,该方法在每次迭代时返回一个包含 value 和 done 属性的对象,用于表示当前迭代的值和迭代是否已经结束。
使用这种约定的方式,JavaScript 可以支持各种不同类型的迭代器,而无需强制它们实现特定的接口。
接口(Interface)的实现通常需要明确定义一组方法和属性,而约定式的设计允许更大的灵活性,使得不同类型的对象可以在特定的上下文中表现出类似的行为,而无需强制它们实现相同的接口。这种设计哲学是 JavaScript 的一部分,它更注重对象的行为而非类型。
1.1 迭代器必须实现next方法,可选择实现return方法。
迭代器的获取,比如这里的counter对象 ,可以通过counter[Symbol.iterator]()获取,调用next方法返回的是IteratorResult对象
let iter = counter[Symbol.iterator]();
console.log(iter.next()); // {done: false, value: 1}
console.log(iter.next()); // {done: false, value: 2}
console.log(iter.return());// {done: true}
console.log(iter.next()); // {done: false, value: 3}
如果一个迭代器对象实现了 return 方法,那么在迭代过程中可以调用它,从而执行清理操作或提前结束迭代。如果没有实现 return 方法,这个方法会简单地返回一个带有 done 属性为 true 的对象,表示迭代结束。
二、来看看生成器的例子
function* nTimes(n) {
if (n > 0) {
yield* nTimes(n - 1);
yield n - 1;
}
}
for (const x of nTimes(3)) {
console.log(x); // 0 1 2
}
用生成器来实现一个递归函数0 1 2;
这里function* 表示它是一个生成器函数,可以使用yield生成值。
2.1 yield* 的返回值
我们来看看chat gpt对yield *的解释:
function* generator1() {
yield 1;
yield 2;
return 3;
}
function* generator2() {
yield 4;
const result = yield* generator1();
yield result + 1;
}
const generator = generator2();
console.log(generator.next()); // { value: 4, done: false }
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 4, done: true }
如果将generator1中的return 3 删除,则会怎样呢?
function* generator1() {
yield 1;
yield 2;
}
function* generator2() {
yield 4;
const result = yield* generator1();
yield result + 1;
yield result + 2;
yield result + 3;
}
const generator = generator2();
console.log(generator.next()); // { value: 4, done: false }
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: undefined, done: true }
2.2 nTimes(3)为什么可以在可迭代对象的位置?
nTimes 是一个生成器函数,而生成器函数默认就实现了 Symbol.iterator 方法。生成器函数返回一个迭代器对象,该对象具有 next() 方法,因此它是一个可迭代对象。
2.3 使用yield实现输入和输出
上一次让生成器函数暂停的yield关键字会接收到传给next方法的第一个值。
function * generator(){
return yield 'foo';
}
let generatorObj = generator();
console.log(generatorObj.next("aaa"));// {value: 'foo', done: false}
console.log(generatorObj.next("bbb"));// {value: 'bbb', done: true}
console.log(generatorObj.next("ccc"));// {value: undefined, done: true}
在你的生成器函数中,return yield 'foo'; 中的 yield 'foo' 部分实际上并没有在第一次调用 next 时执行。生成器函数的执行是惰性的,只有在第一次调用 next 时才开始执行,并且执行到第一个 yield 语句时会暂停,等待下一次调用 next。
分析代码执行过程:
-
第一次调用
generatorObj.next("aaa"): -
开始执行生成器函数,执行到
yield 'foo';时暂停,并返回{ value: 'foo', done: false }。 -
注意,这时候
yield 'foo'并没有真正产生值'foo',而是等待下一次调用next。2. 第二次调用generatorObj.next("bbb"): -
传递参数
"bbb"给上一次暂停的yield 'foo';,此时yield 'foo'表达式的值为"bbb"。 -
生成器函数执行到最后,遇到
return yield 'foo';,这时候return的值为"bbb",生成器函数继续执行,并返回{ value: 'bbb', done: true }。3. 第三次调用generatorObj.next("ccc"): -
由于生成器函数已经执行完毕,再次调用
next不会再执行生成器函数内部的代码,而是直接返回{ value: undefined, done: true }。 有时候我在想,怎么理解这里的yield呢?
function * generator(){
'foo' yield;
return (yield的值);
}
首次调用next触发函数执行,遇到yield就返回,这里返回”foo", 然后再次调用next,则返回“bbb"。
2.4 yield*产生可迭代的值
function* generator() {
yield* [1, 2];
yield* [3];
yield* [4, 5];
}
for (let i of generator()) {
console.log(i); // 1 2 3 4 5
}
这里就相当于串行迭代了。
2.5 生成器也可以是迭代器
class Foo {
*[Symbol.iterator]() {
yield* [1, 2, 3];
}
}
let foo = new Foo();
for (let i of foo) {
console.log(i);// 1,2,3
}
2.6 生成器调用return可以提前终止,遇到throw()也会关闭,捕捉之后可以继续。
function* gen(){
yield* [1,2,3];
}
let foo = gen();
console.log(foo.next());// {value: 1, done: false}
console.log(foo.return());// {value: undefined, done: true}
console.log(foo.next());// {value: undefined, done: true}
function* gen() {
for (const x of [1, 2]) {
yield x;
}
}
let foo = gen();
console.log(foo.next());// {value: 1, done: false}
try{
foo.throw('exception'); // Uncaught exception
}catch(e){
console.log(e);
}
console.log(foo); // gen {<closed>}
function* gen() {
for (const x of [1, 2, 3]) {
try {
yield x;
} catch (e) {
console.log(e);
}
}
}
let foo = gen();
console.log(foo.next());// {value: 1, done: false}
foo.throw('exception'); // Uncaught exception
console.log(foo); // gen {<suspended>}
console.log(foo.next());// {value: 3, done: false}
console.log(foo.next());// {value: undefined, done: true}
如果throw过,在yield捕捉异常,则当前yield会跳过。