generator(生成器)
生成器是ES6最具魔力的特性。为什么这么说?对初学者来说,它与JS已有的特性截然不同,可能第一眼会觉得晦涩难懂。但是,从某种意义上来说,它使语言内部的常规行为变得更加强大。如果这都不是魔力,我不知道还有什么是。
注意:生成器可以简化代码,解决回调地狱。
生成器简介
什么是生成器?从一个示例开始:
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!"; }
它看起来有点像函数,对吗?它叫生成器函数(generator-function),和函数在很多方面一致。但有两处不同:
function
<->function*
- 在生成器函数内,
yield
是关键词,语法和return
相似。区别在,函数(包括生成器函数)只能return
一次,生成器函数可以yield
任意次数。yield
表达式挂起生成器的执行,所以后面可以恢复。
好,这就是很大的不同:常规函数不能暂停自己,但生成器函数可以。
生成器做了什么
当你调用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 }
普通函数,当你调用它们时,它们立即运行,直到遇到return或抛出异常时才退出执行。
调用生成器函数看起来类似:quips("jorendorff")
。但当你调用生成器函数时,它并没有开始运行。相反,它返回一个生成器(Generator)对象(上面的iter
)。你可以将这个生成器对象视为一次函数调用,只不过立即冻结了,它恰好在生成器函数的最顶端(第一行代码之前)冻结了。
每次你调用生成器对象的.next()
方法时,函数调用将其自身解冻并一直运行到下一个yield表达式前,再次暂停。
调用最后一个iter.next()
时,我们最终抵达生成器函数的末尾,所以返回结果中done
的值为true
。抵达函数的末尾就像return undefined
,所以返回结果中value
的值为undefined
。
用专业术语来说,每当生成器执行yields语句,生成器的堆栈帧(stack frame,含有本地变量、参数、临时值、生成器内部当前的执行位置)被移出堆栈。但是,生成器对象保留了对这个帧的引用(或复制),所以稍后调用.next()
可以重新激活帧并且继续执行。
注意:生成器不是线程。
生成器可以暂停,恢复运行。那么问题来了,这有什么用?
生成器是迭代器
结合上一部分的迭代器,我们来构造一个range
迭代器,就简单地从一个数增加到另一个数。
// This should "ding" three times for (var value of range(0, 3)) { console.log("Ding! at floor #" + value); } // 输出 //Ding! at floor #0 //Ding! at floor #1 //Ding! at floor #2
class Range{ constructor(start, stop) { this.value = start; this.stop = stop; } [Symbol.iterator]() {return this;} next() { var value = this.value; if(value < this.stop) { this.value++; return {done: false, value: value}; } else { return {done: true, value: undefined}; } } } function range(start, stop) { return new Range(start, stop); }
上面的实现看起来像Java或Swift,不坏,但也不简洁。看起来迭代器很好用,但实现起来有点麻烦。
有更好的方式吗?用生成器:
function* range(start, stop) { for (var i = start; i < stop; i++) yield i; }
4行代替23行,之所以可行,因为生成器就是迭代器(generators are iterators)。所有的生成器都内置.next()
和[Symbol.iterator]()
的实现。你只要写循环行为。
如何使用生成器作为迭代器的能力?
- 使任意对象可迭代。编写生成器函数遍历这个对象,运行时yield每一个值。然后将这个生成器函数作为这个对象的[Symbol.iterator]方法。
- 简化数组构建函数。比如:
// Divide the one-dimensional array 'icons' // into arrays of length 'rowLength'. function splitIntoRows(icons, rowLength) { var rows = []; for (var i = 0; i < icons.length; i += rowLength) { rows.push(icons.slice(i, i + rowLength)); } return rows; }
可以变成:
function* splitIntoRows(icons, rowLength) { for (var i = 0; i < icons.length; i += rowLength) { yield icons.slice(i, i + rowLength); } }
- 共 1 页
- 1
function fn(arr) {
arr = [...new Set(arr)].sort((a, b) => a - b)
let result = []
let offset = arr[0]
let temp = []
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== i + offset) {
result.push(temp)
temp = [arr[i]]
offset = arr[i] - i
} else {
temp.push(arr[i])
}
}
return result
}
第 67 题:随机生成一个长度为 10 的整数类型的数组