ES6深度解析3:Generators
介绍es6 generators
什么是generators(生成器函数)?让我们先来看看一个例子。
function* quips(name) { yield "hello " + name + "!"; yield "i hope you are enjoying the blog posts"; if (name.startswith("x")) { yield "it's cool how your name starts with x, " + name; } yield "see you later!"; }
这是一只会说话的猫的一些代码,可能是当今互联网上最重要的一种应用。它看起来有点像一个函数,对吗?这被称为生成器-函数,它与函数有很多共同之处。但你马上就能看到两个不同之处。
- 普通函数以function开头,生成器函数以function*开头
- 在生成器函数中,yield是一个关键字,语法看起来像return。不同的是,函数(甚至是生成器函数)只能返回一次,而生成器函数可以“yield”任何次数。yield表达式暂停了生成器的执行,同时它可以在以后再次恢复。
generators可以做什么
当你调用生成器-函数quips()时会发生什么?
> var iter = quips("jorendorff"); [object generator] > iter.next() { value: "hello jorendorff!", done: false } > iter.next() { value: "i hope you are enjoying the blog posts", done: false } > iter.next() { value: "see you later!", done: false } > iter.next() { value: undefined, done: true }
你可能已经非常习惯于普通函数和它们的行为方式。当你调用它们时,它们会立即开始运行,并一直运行到返回或抛出异常。所有这些对任何js程序员来说都是第二天性。调用一个生成器看起来也是一样的:quips("jorendorff")。但是当你调用一个生成器时,它还没有开始运行。相反,它返回一个暂停的generator对象iter(就是在上面的例子中叫做iter的对象)。你可以把这个generator对象看作是一个函数调用,在调用前被冻结。具体来说,它被冻结在生成器函数的顶端,就在运行其第一行代码之前。每次你调用generator对象的方法.next()时,函数调用都会自我解冻,并运行到下一个yield表达式为止。这就是为什么我们每次调用上面的iter.next()方法,都会得到一个不同的字符串值。这些都是由函数quips()中的yield表达式产生的值。在最后一次iter.next()调用中,我们终于到达了生成器-函数的终点,所以结果中.done字段的值是true。到达生成器函数的终点就像普通函数返回undefined一样,这就是为什么结果的value字段值是undefined。
现在可能是一个好时机,回到会说话的猫的演示页面,真正地玩一玩代码。试着把yield放在一个循环里面。会发生什么?从技术上讲,每次generator执行yield时,它的堆栈--局部变量、参数、临时值以及当前在generator主体中的执行位置--都会从堆栈中删除。然而,generator对象会保留对这个堆栈框架的引用(或副本),以便以后.next()调用可以重新激活它并继续执行。
值得指出的是,generator不是线程。在有线程的语言中,多段代码可以同时运行,通常会导致竞赛条件、非确定性和甜蜜的性能。generator则完全不是这样的。当一个generator运行时,它与调用者在同一个线程中运行。执行的顺序是顺序的、确定的,而不是并发的。与系统线程不同,generator只在其函数体中标明的yield点上暂停运行。
好了。我们知道generator是什么。我们已经看到了一个generator的运行,暂停自己,然后恢复执行。现在有个大问题。这种奇怪的能力怎么可能有用?
generators就是迭代器(generators are iterators)
es6迭代器不仅仅是一个单一的内置类。它们是该语言的一个扩展点。你可以通过实现两个方法symbol.iterator和next()来创建你自己的迭代器。但是实现一个接口至少要做一点工作。让我们看看迭代器的实现在实践中是什么样的。作为一个例子,让我们做一个简单的迭代器range,它只是从一个数字到另一个数字进行计数,就像一个老式的c循环for (;
上一篇: Django个人博客系统(1-5)
下一篇: 软件设计七大原则