我试图更好地了解生成器在javascript中的工作方式。
来自MDN:
function *声明(function关键字后跟一个星号) 定义一个生成器函数,该函数返回一个Generator对象。
function *range(from, to) {
var counter = from;
while(to >= counter) {
yield counter
counter++
}
}
for (var r of range(5, 10)) {
console.log( r );
}
// print: 5, 6, 7, 8, 9, 10
我不确定我如何确切地理解上面的代码片段中发生的事情。
不应该调用生成器,将其存储为(生成器)对象,然后通过next()
方法进行调用。 (如下所示)
function *foo () {
yield 'woo';
}
var G = foo();
console.log( G.next() );
在上面的代码中,第4行带有var G = foo();
,我不调用一个函数并创建新的执行上下文,这应该只返回一个生成器对象(并将其存储在标签下) G
)。
当我在第5行调用foo
方法时,我将调用实际的函数next()
。那时,我正在创建一个执行上下文,在{中执行代码{1}}并产生字符串foo
。
第一个代码段应该如何工作?
答案 0 :(得分:3)
调用生成器函数将返回一个迭代器(具有.next
函数的对象),并且for..of
循环将自动迭代可迭代对象。当您可以将迭代器预先存储在变量中时:
const iter = range(5, 10);
for (var r of iter) {
...
}
不必要-for..of
毕竟只需要对迭代器的单个引用。
您可以通过将对迭代器的单个引用传递到调用每个.next
函数直到迭代器用尽的函数中,以在代码中进行模仿:
function *range(from, to) {
var counter = from;
while(to >= counter) {
yield counter
counter++
}
}
iterate(range(5, 10), num => {
console.log(num);
});
function iterate(iterator, callback) {
while (true) {
const { value, done } = iterator.next();
if (done) return;
callback(value);
}
}
如您所见,在将迭代器传递到iterate
之前,无需将其存储在变量中,就像您可以将range(5, 10)
调用直接用于for..of
循环一样,因为循环(或函数)的内部为您完成了所有迭代。
答案 1 :(得分:1)
因此,对您的问题的简短回答是,for ... of loop实际上仅期望符合Iterable Protocol的对象。这是一个对象,具有绑定到返回可迭代对象的符号键Symbol.iterator
的功能。
生成器实际上具有此属性和 next
函数(iterator protocol的实现需要此属性)。您可以在以下代码段中看到它。
const f = function*() {
let i = -1;
while(true){
i = i + 1;
yield i;
}
};
const generator = f();
console.log(generator[Symbol.iterator]);
console.log(generator.next);
因此,这就是为什么您无需为for...of
循环创建生成器对象的实例,也无需显式调用next
的原因。由于Iterable协议的约定,这可以自动处理。
话虽如此,您可以通过以下方式从生成器对象创建迭代器对象(或直接调用for...of
)来模仿while
循环和next
循环的行为:
const f = function*() {
let i = -1;
while(true){
i = i + 1;
yield i;
}
};
const generator = f();
const iterator = generator[Symbol.iterator]();
let j = 0;
let next;
while(j < 5) {
next = iterator.next();
console.log('next: ', next);
j = next.value;
}
原则上,这也可能是for...of
循环的本机代码。