var a = 3;
var b = 2;
function foo() {
a = a * 2;
bar();
b++;
console.log(a, b); // 7, 5
}
function bar() {
a++;
b = b * 2;
}
上面代码中 bar 函数很直观地在 b++ 与 a * 2 之间运行,现在假设一下,bar 现在不存在于 foo 函数里,那么怎么才能使单线程的 JS 中断 foo 函数去执行bar ,然后再回来继续执行自己没有执行的代码呢
对于上面的问题,只要我们能够通知 foo 在某个地方暂停下来就可以实现这样的需求
var a = 3;
var b = 2;
function* foo() {
// 声明生成器
a = a * 2;
yield;
b++;
console.log(a, b);
}
function bar() {
a++;
b = b * 2;
}
有些地方使用的是function *foo(){...} 的形式
那么现在要如何运行代码才能达到想要的效果呢
/* 构造一个迭代器it来控制生成器foo,此时foo完全没有被执行 */
var it = foo();
/* foo开始执行,一直到遇到yield才停止,此时a = 6, b = 2 */
it.next();
console.log(a, b); // 6, 2
/* foo让出线程,执行bar,此时a = 7, b = 4 */
bar();
console.log(a, b); // 7, 4
/* foo继续执行,此时a = 7, b = 5 */
it.next(); // 7, 5
生成器是一类特殊的函数,可以一次或多次启动与停止,而且不一定非要完成
生成器特殊归特殊,它始终都是一个函数,依然具有一些函数的基本特性,例如接受参数(输入),返回值(输出)
function* foo(x, y) {
return x * y;
}
var it = foo(6, 7);
var res = it.next();
console.log(res);
// {value: 42, done: true}
next 的调用结果是一个对象,拥有一个 value 属性,持有从 foo 返回的值,也就是说,yield 会导致生成器在执行过程中发送出一个值,有点类似上面的return
除了能接收参数并提供返回值,生成器还提供了更强大的内建消息输入输出能力,通过 yield 和 next 实现
function* foo(x) {
var y = x * (yield);
return y;
}
var it = foo(6);
it.next();
var res = it.next(7);
console.log(res);
// { value: 42, done: true }
首先 6 作为参数 x,调用it.next() ,启动foo
在foo 内部,执行语句var y = x... 时遇到yield 表达式,在此处(赋值语句中间)暂停foo ,并要求调用代码为yield 的表达式提供一个结果值,接下来调用it.next(7) ,这一句把 7 传回作为被暂停的yield 表达式的结果
一般来讲,next 的数量要比yield 的数量多一个,这在理解代码时会给人一种不协调不匹配的感觉
只考虑生成器代码
var y = x * yield;
return y;
第一个yield 提出一个问题:我这里应该插入什么值?
谁来回答这个问题呢,第一个next 已经运行使得生成器启动并运行到此处,它显然无法回答这个问题,因此必须由第二个next 调用回答第一个yield 提出的问题
这就是不匹配——第二个next对第一个yield?
接下来转换一下视角,从迭代器角度看问题
在此之前需要再次解释以下yield的消息传递,它是双向的,前面我们只提了next向暂停的yield发送值,下面看一下yield发出消息供next使用
function* foo(x) {
var y = x * (yield "hello world");
return y;
}
var it = foo(6);
/* 第一个next不传入任何东西,实际上就算传了也会被浏览器悄咪咪丢掉 */
var res = it.next();
console.log(res); // { value: 'hello world', done: false }
/* 给等待的yield传一个7 */
res = it.next(7);
console.log(res); // { value: 42, done: true }
第一个next 调用提出一个问题:foo要给我的下一个值是什么?
谁来回答呢?第一个yield "hello world" 表达式
这里没有不匹配的问题
但是,对于最后一个next ,也就是it.next(7) 提出的foo要给我的下一个值是什么的问题,没有yield 来回答它了,那么由谁来回答呢
答案是return 语句
如果生成器中没有return,则会有隐式的return undefined 来回答
从语法使用的方面来看,通过一个迭代器控制生成器的时候,似乎是在控制声明的生成器函数本身,但有一个细节需要注意,每次构建一个迭代器,实际上就隐式构建了生成器的一个实例,通过这个迭代器来控制其对应的生成器实例
同一个生成器的多个实例可以同时运行,甚至彼此交互
function* foo(i) {
var x = yield 2;
z++;
var y = yield x * z;
console.log({ i, x, y, z });
}
var z = 1;
var it1 = foo(1);
var it2 = foo(2);
var val1 = it1.next().value;
var val2 = it2.next().value;
console.log({ val1, val2 }); // { val1: 2, val2: 2 }
val1 = it1.next(val2 * 10).value; // z = 2, 1中x = 20, val1 = x * z = 40
val2 = it2.next(val1 * 5).value; // z = 3, 2中x = 200, val2 = x * z = 600
console.log({ val1, val2 }); // { val1: 40, val2: 600 }
it1.next(val2 / 2); // { i: 1, x: 20, y: 300, z: 3 }
it2.next(val1 / 4); // { i: 2, x: 200, y: 10, z: 3 }
再来看一个例子
var a = 1;
var b = 2;
function foo() {
a++;
b = b * a;
a = b + 3;
}
function bar() {
b--;
a = 8 + b;
b = a * 2;
}
上面是一串普通的 js 代码,对于普通的 JS 函数,显然要么先执行 foo,要么先执行 bar,二者不能交替执行
但是,使用生成器的话,交替执行显然是可能的
var a = 1;
var b = 2;
function* foo() {
a++;
yield;
b = b * a;
a = (yield b) + 3;
}
function* bar() {
b--;
yield;
a = (yield 8) + b;
b = a * (yield 2);
}
根据每一步调用的相对顺序的不同,上面的程序能产生多种不同的结果
首先构建一个辅助函数来控制迭代器
function step(gen) {
// 初始化一个生成器来创建迭代器it
var it = gen();
var last;
// 返回一个函数,每次被调用都会将迭代器向前迭代一步,前面yield发出的值会在下一步发送回去
return function () {
// 不管yield出来的是什么下一次都把它原原本本的传回去
last = it.next(last).value;
};
}
下面我们先从最基本的情况开始,让 foo 在 bar 之前执行结束
var s1 = step(foo);
var s2 = step(bar);
// 执行foo
s1();
s1();
s1();
// 执行bar
s2();
s2();
s2();
s2();
console.log(a, b); // 11, 22
然后我们使二者交替进行
var s1 = step(foo);
var s2 = step(bar);
s2();
// b--, b = 1, 遇到yield,暂停!last接受第一个yield发出的值undefined
s2();
// 第一个yield返回值为undefined(无影响),执行a = ...遇到yield,暂停!last 接受第二个yield发出的值8
s1();
/* a++, a = 2, 遇到yield,暂停!last接受第一个yield发出的值undefined */
s2();
// 第二个yield返回值为8,a = 8 + b = 8 + 1 = 9, 执行b = 9 * ...遇到yield,暂停!last 接受第三个yield发出的值2
s1();
/* 第一个yield返回值为undefined(无影响),b = b * a = 1 * 9 = 9, 执行a = ..., 遇到yield,暂停!last接受第二个yield发出的值b,也就是9 */
s1();
/* 第二个yield返回值为9,a = 9 + 3 = 12,s1执行完毕,foo结束 */
s2();
// 第三个yield返回值为2,b = 9 * 2 = 18,s2执行完毕,bar结束
console.log(a, b); // 12, 18
前面说了那么多“迭代器”与“生成器”,现在具体来谈一谈这二者是什么东东
在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。更具体地说,迭代器是通过使用 next() 方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象:value,这是序列中的 next值;和 done,如果已经迭代到序列中的最后一个值,则它为 true 。如果 value 和 done 一起存在,则它是迭代器的返回值。
从 ES6 开始,从一个 iterable 中提取迭代器的方法是:iterable 必须支持以后函数,其名称是Symbol.iterable ,调用这个函数时,它会返回一个迭代器,通常每次调用会返回一个全新的迭代器
后面我们根据例子具体分析
假定现在你要产生一系列值,每一个值都与前面一个有特定的关系,要实现这一点,需要一个有状态的生产者能记住其生成的最后一个值
先用闭包整一个
var getSomething = (function () {
var nextVal;
return function () {
if (nextVal === undefined) nextVal = 1;
else nextVal = nextVal * 3 + 6;
console.log(nextVal);
return nextVal;
};
})();
getSomething();
getSomething();
getSomething();
getSomething();
再试着对它修改,将它改为一个标准的迭代器接口
var getSomething = (function () {
var nextVal;
return {
[Symbol.iterator]: function () {
return this;
},
next: function () {
if (nextVal === undefined) nextVal = 1;
else nextVal = nextVal * 3 + 6;
return { done: nextVal > 500, value: nextVal };
},
};
})();
getSomething.next().value; // 1
getSomething.next().value; // 9
getSomething.next().value; // 33
getSomething.next().value; // 105
其中有些令人疑惑的代码大概就是[Symbol.iterable]: function() {return this} 这一行了
这一行的作用是将getSomething 的值也构建成为一个 iterable,现在它既是 iterable 也是迭代器
for (let v of getSomething) {
console.log(v);
}
/*
1
9
33
105
321
*/
可以把生成器看作一个值的生产者,我们通过迭代器接口的next 调用一次提取出一个值,所以严格来讲,生成器本身并不是 iterrable,尽管执行一个生成器就得到了一个迭代器
如果使用生成器来实现前面的getSometing :
function* getSomething() {
var nextVal;
while (true) {
if (nextVal === undefined) nextVal = 1;
else nextVal = nextVal * 3 + 6;
yield nextVal;
}
}
生成器会在每个yield 处暂停,函数getSomething 的作用域会被保持,也就意味着不需要闭包在调用之间保持变量状态
上面代码也同样可以使用for...of 循环
// 注意这里是getSomething(),得到了它的迭代器来进行循环的
for (var v of getSomething()) {
console.log(v);
// 不要死循环!
if (v > 500) {
break;
}
}
但是上面的代码看起来getSomething 在break执行之后被永远挂起了。
不过并非如此,for...of 循环的“异常结束”,通常由 break,return 或者未捕获异常引起,会向生成器的迭代器发送一个信号使其终止。
来自:https://mp.weixin.qq.com/s/IdvvFfvgrwAt7DTJKnxGYA
Generator与async function都是返回一个特定类型的对象:Generator: 一个类似{ value: XXX, done: true }这样结构的Object,Async: 始终返回一个Promise,使用await或者.then()来获取返回值
由于Generator可以暂停函数执行,返回任意表达式的值,这使得 Generator有多种应用场景,这篇文章简单整理一些Generator的使用场景:异步操作的同步化表达、控制流管理、部署 Iterator 接口、作为数据结构
Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
Generator函数是ES6标准中提出的一种异步编程的解决方案。这种函数与普通函数最大的区别在于它可以暂停执行,又可以从暂停的位置恢复继续执行。从语法上看,Generator函数就是一个状态机,封装了许多内部状态。
es6 generator函数,我们都知道asycn和await是generator函数的语法糖,那么genertaor怎么样才能实现asycn和await的功能呢?thunk函数 将函数替换成一个只接受回调函数作为参数的单参数函数
状态机,封装了多个内部状态;返回一个遍历器对象,通过改对象可以一次遍历Generator函数内部的每一个状态;带*号,yeild表达式定义不同的内部状态;调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果
最近,为了更好地理解Redux Sagas的工作原理,我重学了JavaScript generators的知识,我把从网上收集到的各种知识点浓缩到一篇文章里,我希望这篇文章既通俗易懂,又足够严谨,可以作为初学者的generators使用指南。
实际上Generator就是遍历器的一个生成器,我们可以用Generator来生成一个遍历器。Generator有两个明显的特点:第一个是function关键字与函数名之间有一个星号,一般而言是将两者写在一起的。第二个是在函数体内部有一个yield的关键字
虽然现在使用 async 函数 就可以替代 Generator 执行器了,不过了解下 Generator 执行器的原理还是挺有必要的。如果你不了解 Generator,那么你需要看这里。
这是一只会说话的猫的一些代码,可能是当今互联网上最重要的一种应用。它看起来有点像一个函数,对吗?这被称为生成器-函数,它与函数有很多共同之处。但你马上就能看到两个不同之处。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!