简介
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同 。
在Javascript中,一个函数一旦开始执行,就会运行到最后或遇到return时结束,运行期间不会有其它代码能够打断它,也不能从外部再传入值到函数体内
而Generator函数(生成器)的出现使得打破函数的完整运行成为了可能,其语法行为与传统函数完全不同
可迭代协议
可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of
结构中,哪些值可以被遍历到。一些内置类型同时是内置可迭代对象,并且有默认的迭代行为,比如 Array
或者 Map
,而其他内置类型则不是(比如 Object
))。
要成为可迭代对象, 一个对象必须实现 @@iterator
方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator
的属性,可通过常量 Symbol.iterator
访问该属性:
[ Symbol.iterator
] 一个无参数的函数,其返回值为一个符合迭代器协议的对象。
迭代器协议
迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。
只有实现了一个拥有以下语义(semantic)的 next()
方法,一个对象才能成为迭代器:
next | 一个无参数函数,返回一个应当拥有以下两个属性的对象:
如果迭代器可以产生序列中的下一个值,则为 如果迭代器已将序列迭代完毕,则为
迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
|
String
是一个内置的可迭代对象:
let someString = "hi";
typeof someString[Symbol.iterator];
let iterator = someString[Symbol.iterator]();
iterator + ""; // "[object String Iterator]"
iterator.next(); // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }
可迭代对象
function createIterator(arr) {
var i = 0
return {
next: function() {
var done = i >= arr.length;
var value = !done ? arr[i] : undefined;
i++
return {value, done};
}
}
}
var interator = createIterator([1,3,4,3])
调用函数后生成一个可迭代对象,上面有next方法,返回{value, done}
console.log(interator.next()) // {value: 1, done: false}
console.log(interator.next()) // {value: 3, done: false}
console.log(interator.next()) // {value: 4, done: false}
console.log(interator.next()) // {value: 3, done: false}
console.log(interator.next()) // {value: undefined, done: true}
使用
1直接只用
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
2 作为表达式
作为表达式使用 需要加括号,不然会报错
function * demo() {
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
var d = demo()
console.log(d.next())
console.log(d.next())
console.log(d.next())
3 yiel*
控制权交给另外一个生成器函数。调用生成器函数,返回值是一个带有
function *flat(arr) {
for(var i = 0; i < arr.length; i ++) {
if(typeof i === 'number') {
yield i
} else {
yield* flat(i) // 交出迭代器的控制权
}
}
}
var f = flat(arr)
// console.log(f.next())
// console.log(f.next())
// console.log(f.next())
// console.log(f.next())
for(var j of f) {
console.log(j)
}
console.log(f.next())
console.log(f.next())
console.log(f.next())
console.log(f.next())
这个时候循环失效。
4 next函数带参数
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
console.log(a.next()) // {value: 6, done: false}
console.log(a.next(24)) // {value: 16, done: false}
console.log(a.next(20)) // {value: 73, done: true} 5 + 48 + 20
5 return 函数
function *genarator() {
yield 1;
yield 12;
}
var interators = genarator();
console.log(interators.next())
console.log(interators.return()) // 提前让生成器结束掉
6 throw函数
让生成器内部抛出错误
function *generator() {
let a = 1;
try {
yield 3
} catch (error) {
a = 4
}
yield a + 1
}
var int = generator()
console.log(int.next()) // {value: 3, done: false}
console.log(int.throw()) // {value: 5, done: false}
实现原理
- 一个线程存在多个协程
- Generator函数是协程在ES6的实现
- Yield挂起X协程(交给其它协程),next唤起协程
什么是协程呢?
协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。
协程不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。这样带来的好处是性能大幅度的提升,因为不会像线程切换那样消耗资源。
协程不是进程也不是线程,而是一个特殊的函数,这个函数可以在某个地方挂起,并且可以重新在挂起处外继续运行。所以说,协程与进程、线程相比并不是一个维度的概念。
自动执行Generator函数
先来理解一下thunk函数。
调用一个读取文件的函数:
fs.readFile(filename, callback)
改成下面的方式也可以实现文件的读取:
var thunk = function(filename) {
return function(callback) {
return fs.readFile(filename, callback)
}
}
thunk(filename)(callback)
这种就是thunk函数的写法,变成更通用的:
var thunk = function(fun) {
return function() {
var args = Array.prototype.slice.call(arguments)
return function(callback) {
fun.apply(null, args, callback)
}
}
}
var readFileThunk = thunk(fs.readFile)
看起来没有多大用处,但是用到Generator函数中,就可以让它自动执行:
function* gen () {
var result1 = yield readFileThunk('fileA')
var result2 = yield readFileThunk('fileB')
var result3 = yield readFileThunk('fileC')
console.log('执行完毕')
}
function run(gen) {
var g = gen();
var next = function() {
var result = g.next()
if(result.done) return;
result.value(next)
}
next()
}
run(gen)
thunk的函数的关键是需要有callback函数,需要在合适的时候调用callback才可以循环执行。
需要更详细的了解 Generator函数,可以参考文档:http://www.ruanyifeng.com/blog/2015/05/thunk.html