我们先看es6里的是这样子:
//Generator函数
function* chain(){
yield 'a';
console.log('sss');
yield 'b';
yield 'c';
return 'end';
}
var exp=chain()
console.log(typeof(exp))
//value是yield后面的值,done表示的是当前函数是否执行完毕
console.log(exp.next())//{value: "a", done: false}
console.log("附件打开");
// 注意,执行b的next的时候,才打印sss
console.log(exp.next())//{value: "b", done: false}
console.log(exp.next())//{value: "c", done: false}
console.log(exp.next())//{value: "end", done: true}
然后生成es5是这样:
'use strict';
var _marked = /*#__PURE__*/regeneratorRuntime.mark(chain);
//Generator函数
function chain() {
return regeneratorRuntime.wrap(function chain$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'a';
case 2:
console.log('sss');
_context.next = 5;
return 'b';
case 5:
_context.next = 7;
return 'c';
case 7:
return _context.abrupt('return', 'end');
case 8:
case 'end':
return _context.stop();
}
}
}, _marked, this);
}
var exp = chain();
//value是yield后面的值,done表示的是当前函数是否执行完毕
console.log(exp.next()); //{value: "a", done: false}
console.log("附件打开");
// 注意,执行b的next的时候,才打印sss
console.log(exp.next()); //{value: "b", done: false}
console.log(exp.next()); //{value: "c", done: false}
console.log(exp.next()); //{value: "end", done: true}
可以看到,编译器/运行时会对yield进行转换/打洞,将每个yield拆分到多个块里(yield行和之上行的代码[不包括上一次yield]会作为一个计算块);
这里其实regeneratorRuntime.wrap(...)返回的就类似C# IEnumerable<Object>一样的状态机/迭代器/生成器;
里面有context对象,context对象又记录了prev和next两个"游标",当执行next的时候(C#里的for xx in iter本质上也是执行了iter的next())会将自己的context作为参数调用chain$,然后根据游标值来看这次next是要执行哪个计算块;
然后是C#的代码,无yield:
// See https://aka.ms/new-console-template for more information
IEnumerable<int> fibonaccis = Fibonacci(4);
foreach (var f in fibonaccis)
{
Console.Write("{0} ", f);
}
//计算斐波拉契数据
IEnumerable<int> Fibonacci(int count)
{
int p= 1;
int c= 1;
List<int> result = new List<int>();
for (int i = 0; i < count; i++)
{
result.Add(p);
Thread.Sleep(2000);
int temp = p+ c;
p= c;
c= temp;
}
return result;
}
// yield: 1 1 2 3 Hello, World!Program+<<<Main>$>g__Fibonacci|0_0>d
// 无yield: 1 1 2 3 Hello, World!System.Collections.Generic.List`1[System.Int32]
Console.WriteLine("Hello, World!" + fibonaccis.GetType());
有yield:
// See https://aka.ms/new-console-template for more information
IEnumerable<int> fibonaccis = Fibonacci(4);
foreach (var f in fibonaccis) // 每次in赋值给f时,都是执行了类似next获取下一个元素的方法,而这个next的方法就类似es5里的chain$
{
Console.Write("{0} ", f);
}
//计算斐波拉契数据
IEnumerable<int> Fibonacci(int count)
{
int p= 1;
int c= 1;
for (int i = 0; i < count; i++)
{
yield return p;
Thread.Sleep(2000);
int temp = p+ c;
p= c;
c= temp;
}
}
// yield: 1 1 2 3 Hello, World!Program+<<<Main>$>g__Fibonacci|0_0>d
// 无yield: 1 1 2 3 Hello, World!System.Collections.Generic.List`1[System.Int32]
Console.WriteLine("Hello, World!" + fibonaccis.GetType());
可以看出有yield和无yield的IEnumerable<int>的实现类是不一样的,必须的不一样,因为fibonaccis提取下一个元素的方法(假设叫next())next在非yield里其实就类似从集合里取值,而yield的next方法则是内部又调用了一个类似es5里的chain$的方法来取值(会传context给chain$),即两种得到IEnumerable<int>对象的取值逻辑都不一样,自然其实现类是不会一样的;
这里yield最重要的实现方式之一就是编译器会自动对每个yield进行拆计算块,即第一个计算块是函数开始到第一个yield那行算一个计算块(这个是编译器/运行时会去拆,至于为什么它能拆的出来就涉及编译器/运行时实现太高深了),第二个计算块则是第一个yield后面的代码到第二个yield那行代码;第三个计算块则是第二个yield后面的代码到第三个yield那行,以此类推(每个计算块会做额外操作,参考es5里还有对context.next赋值的操作);
如果遇到了yield break (类似js的return),则这个计算块会添加对context终止的额外代码,会导致iter.hasNext()为false,从而不会再执行next()来去对下一个元素提取;
标签:ES6,console,log,C#,yield,next,context,return From: https://www.cnblogs.com/silentdoer/p/16864326.html