如何模拟JavaScript中的yield?

16

JavaScript 1.7 中提供了一种新的机制——yield,对于生成器和迭代器非常有用。

目前仅在 Mozilla 浏览器中支持(据我所知)。在不支持此功能的浏览器中模拟此行为的一些方法是什么?


1
虽然这个问题已经得到了回答,但我很惊讶没有人提到 regenerator - Travis Kaufman
5个回答

19

你可以编写一个外层函数,在闭包中初始化变量,然后返回一个执行所需操作的对象。

function fakeGenerator(x) {
  var i = 0;
  return {
    next: function() {
      return i < x ? (i += 1) : x;
    }
  };
}

现在你可以写:

var gen = fakeGenerator(10);

然后一遍又一遍地调用 gen.next()。模拟“close()”方法的“finally”行为对于真实生成器比较棘手,但是您可能会接近某个解决办法。


8
与Pointy的答案类似,但增加了一个hasNext方法:
MyList.prototype.iterator = function() { //MyList is the class you want to add an iterator to

    var index=0;
    var thisRef = this;

    return {
        hasNext: function() {
            return index < thisRef._internalList.length;
        },

        next: function() {
            return thisRef._internalList[index++];
        }
    };
};

hasNext 方法可以让您像这样循环:

var iter = myList.iterator() //myList is a populated instance of MyList
while (iter.hasNext())
{
    var current = iter.next();
    //do something with current
}

1
谢谢Robert,赞成添加hasNext。 - sworoc
我正确地使用了吗? function MyList(arr){ this.arr= arr } MyList.prototype.iterator = function() { //MyList是你想要添加迭代器的类 debugger var index=0; var thisRef = this.arr;return { hasNext: function() { return index < thisRef.length; }, next: function() { return thisRef[index++]; } };}; var myList= new MyList([2,67,89]) var iter = myList.iterator() //myList是MyList的一个实例 while (iter.hasNext()) { var current = iter.next(); console.log(current) } - zloctb
@zloctb,评论中的内容有点难读懂,而且我已经三年没有从事JS职位了,但是这看起来是正确的。它能工作吗? - Robert Gowland

6
对于一个非平凡的生成器函数,您需要使用某种工具将代码转换为ES3等效形式,以便在任何现代浏览器中运行。我建议尝试一下Traceur,它可以粗略地描述为一个ES6到ES3源代码翻译器。因为生成器是ES6的语言特性,Traceur可以为您翻译它们。
Traceur提供了一个演示页面,在这个页面上您可以键入ES6代码并实时查看ES3代码。如果您输入简单的内容如下:
// Note that this declaration includes an asterisk, as specified by current ES6
// proposals. As of version 16, Firefox's built-in support for generator
// functions does not allow the asterisk.
function* foo() {
  var n = 0;
  if (n < 10) {
    n++;
    yield n;
  }
}

for (var n of foo()) {
  console.log(n); 
}

您会发现等效的ES3代码并不简单,它需要包含traceur.runtime以便在浏览器中正确运行代码。运行时在http://traceur-compiler.googlecode.com/git/src/runtime/runtime.js中定义,目前大小为14K(未经压缩)。这是相当大的一部分代码,但很可能使用Closure Compiler可以优化掉大部分代码。
请注意,还有一个错误文件提供了一个选项来内联来自traceur.runtime命名空间的必需函数,这将消除完全包含runtime.js的需要:https://code.google.com/p/traceur-compiler/issues/detail?id=119

3
我开始了一个小项目,试图通过一些回调技巧来实现这个目标。由于在“标准”JavaScript中无法创建真正的协程,因此这不是没有一些注意事项,例如:
  • 不可能使其遵循迭代器协议(例如.next()等),
  • 不可能同时迭代多个生成器,
  • 必须小心不要让错误的对象离开生成器范围(例如通过在超时中调用yield - 由于这是“普通”的JavaScript,因此没有语法限制可以防止您这样做),
  • 生成器中的异常有点棘手,
  • 最后但并非最不重要的是,它非常实验性(刚刚开始了几天)。

光明的一面是你有yield! :)

来自MDC页面的斐波那契示例如下:

var fibonacci = Generator(function () {
  var fn1 = 1;
  var fn2 = 1;
  while (1){
    var current = fn2;
    fn2 = fn1;
    fn1 = fn1 + current;
    this.yield(current);
  }
});

console.log(fibonacci.take(10).toArray());

输出:

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

该项目位于BitBucket上,网址为https://bitbucket.org/balpha/lyfe


3

如果没有编译器或预处理器,是不可能的。

你可以做到的最接近的方式是这样的:

function doStuff() {
    var result = { };
    function firstStuf() { ...; result.next = secondStuff; return 42; };
    function secondStuf() { ...; result.next = thirdStuff; return 16; };
    function thirdStuf() { ...; result.next = null; return 7; };
    result.next = firstStuff;
    return result;
}

但是,这很糟糕,真的不能替代。

在it技术方面。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接