◆ function* か GeneratorFunction コンストラクタから特別な関数を作ります
◆ その関数内で yield を return みたいに使うと 一時終了できます
◆ function* 関数から作られた オブジェクトの next メソッドで yield まで実行されます
◆ 2回目以降の next メソッドは続きから実行されます
◆ yield は文じゃなくて式です

yield をちゃんと?使ってみます

昔の採用されなかった ECMA4 にあったとかで Firefox では前から使えました
少し触れただけでちゃんと使ってませんでしたが ECMA6 に含まれている機能なので Chrome でも使えるようになったので使ってみようと思います

基本な使い方

function* g(){
console.log("g:1")
yield 3
console.log("g:2")
yield 10
console.log("g:3")
yield 999
console.log("g:4")
}
var x = g()
console.log("call g")
console.log(x.next())
console.log(x.next())
console.log(x.next())
console.log(x.next())
console.log(x.next())
call g
g:1
Object {value: 3, done: false}
g:2
Object {value: 10, done: false}
g:3
Object {value: 999, done: false}
g:4
Object {value: undefined, done: true}
Object {value: undefined, done: true}
function* で関数を宣言します
return の代わりに yield を使います

function* で定義した関数 g を実行しても g の中身は実行されていないので何も表示はされてません
g を実行して返ってきた値の next メソッドを実行すると g の中身が実行されます

yield は return の代わりなので yield のところまで行くとそこで g の実行が終わります
next メソッドの返り値は オブジェクトで value と done プロパティがあります
value のほうは yield による返り値です

next メソッドを2回以上実行すると 前回の続きから実行されます

g の最後まで実行したら 返り値が
{
done: true,
value: undefined
}
のオブジェクトが返って来ます
yield がないので value は undefined です
done は最後まで行ったかのフラグなので true になります
これ以降は何度 next メソッドを実行しても 同じ値が返って来ます

上の例で g:4 が出てるのでわかると思いますが yield が後ろに無くても 関数の最後までちゃんと実行されています

for of

自分で 返り値の done プロパティが true になるまで for 文を回す処理を書く必要はなくて
for of 文で自動でできます
var y = g()
for(var v of y){
console.log(v)
}
g:1
3
g:2
10
g:3
999
g:4
こっちでは value プロパティをわざわざ取り出す必要もないです
ループ変数に next メソッドの返り値の value の値が入っています

続きから実行

function* は関数なのに実行を途中でやめてあとから続きを実行できるというすぐれものです
プログラムを書き始めた頃は 関数を途中で止めて あとで続きをできればなーとよく思ったものです

最近では 関数は短くて 引数に対して同じ値を返すものでできるだけ外部に変更を加えないって考えがいいと思ってるのであまり必要ないといえばないのですけど こういう便利機能が増えるのは嬉しいものです


最初は yield はこういう使い方だけだと思ってたのですが もうちょっと便利な「続きからやってる」感のあることができました
function * gen(a){
console.log(a)
var b = yield a+1
console.log(b)
var c = yield b+1
console.log(c)
}
var x = gen(1000)
var t = 9999
console.log("#", t = x.next(t+10).value)
console.log("#", t = x.next(t+10).value)
console.log("#", t = x.next(t+10).value)
function * で gen を定義してます
* の前にスペースがあっても問題なしです

yield の結果を変数に入れています
こうすると next メソッドの引数で渡されたものを受け取れます

最初だけが特別で next じゃなくて gen 関数を実行するときの引数で値を渡します
ここでは複数渡すことが出来ます

一回目の next メソッドの引数は受け取れないので捨てられます
ニ回目以降の next メソッドの引数は yield の評価結果として続きが実行されます
ただし next メソッドに複数引数書いても yield 側で受け取れないので 配列やオブジェクトにしないと消えてしまうようです

こう書いてみると next を書いてる側から gen を呼び出してるはずなのに gen 側からメインとなる処理側を呼び出してるようにも見えます
function * で作った関数の方をメインとして使うという考え方での実装もありそうです

サンプル
function * gen(x, y){
var vx = 0
var vy = -10
var d
while(true){
d = yield {x,y}
vx += d.x
vy += d.y
x += vx
y += vy
}
}
x = gen(100,100)
console.log(x.next({x:0,y:0}).value)
console.log(x.next({x:1,y:2}).value)
console.log(x.next({x:1,y:2}).value)
console.log(x.next({x:1,y:2}).value)
console.log(x.next({x:1,y:2}).value)
Object {x: 100, y: 100}
Object {x: 101, y: 92}
Object {x: 103, y: 86}
Object {x: 106, y: 82}
Object {x: 110, y: 80}
式どんなだったか覚えてないですが 加速度ぽいこともできますね
next の x と y で一回あたりの増減量を調整できます

yield は式なので

yield は評価結果が next の引数になるという特殊なものですが構文的には yield は return のような「文」じゃなくて「式」です
なので
function * gen(){yield 1, 2}
var x = gen()
console.log(x.next())
Object {value: 1, done: false}
こうなります

return だと 1, 2 は 2 と評価されて 2 が返り値になります
yield では結合が (yield 1), 2 となるので next メソッドの返り値では 1 です


式なので条件演算子に混ぜて
var a = flag ? (yield val) : val
なんてこともできます

funciton に yield

function * で定義してますが 普通に function だとどうなるのか気になったので試してみました
function a(){yield 1}
Uncaught SyntaxError: Unexpected number(…)
予期せぬ数値と言われます
function a(){yield}
a()
Uncaught ReferenceError: yield is not defined(…)
yield だけだと定義はできますが 実行すると yield が未定義と言われます

予約後ではなくてただの変数名扱いになってるようです

function* に return

次は反対に function * で作った関数に return をしてみます
function * g(){
return 1
yield 2
}
var x = g()
console.log(x.next())
console.log(x.next())
Object {value: 1, done: true}
Object {value: undefined, done: true}
return が yield のように値を返してそこで終わります

それでだけじゃなく done が true になっています
なので次に next メソッドを実行してもそれ以降にコードがあっても実行されません
return で強制終了も使えるとなると 複雑なことが出来そうな予感がします

function* 構文で作られるもの

Generator Function

function * で定義される関数ってなんなのかをみてみます
var x = function * (){}
console.log(x)
console.log(x.constructor)
function* (){}
GeneratorFunction() { [native code] }

ただ console.log するだけでは関数みたいな表示になってよくわからないので constructor を見てみます

Function ではなく GeneratorFunction となっています

コンストラクタに直接アクセスできるのかなとためしてみると
window.Function // Function() { [native code] }
window.GeneratorFunction // undefined
ムリなようです

ならば と インスタンスの constructor から new します
var x = function * (){}
var GF = x.constructor
var g = new GF("a", "yield a+1")
console.log(g)
console.log(g(10).next())
function* anonymous(a
/**/) {
yield a+1
}
Object {value: 11, done: false}
なんか Function と同じ感じで使えるようです
第一引数(最後以外の引数)は引数になって 第二引数(最後の引数)は関数の本体になります

prototype チェーン

せっかくなのでプロトタイプチェーンも調べてみました

GeneratorFunction → Function
とつながっています
g.__proto__ // GeneratorFunction.prototype
g.__proto__.__proto__ // Function.prototype

なので関数のメソッドが使えるようです

また GeneratorFunction.prototype をみてみると
GeneratorFunction
arguments: (...)
caller: (...)
constructor: GeneratorFunction()
prototype: Object
Symbol(Symbol.toStringTag): "GeneratorFunction"
__proto__: function () {}
で next メソッドがありませんでした
代わりに GeneratorFunction.prototype.prototype にあります

GeneratorFunction のインスタンスの実行結果に next があるわけですし GeneratorFunction.prototype にあるほうがおかしいですね
var g = new GF()
var x = g()
x.next()
こう使うので g を実行したときに返すオブジェクトの __proto__ に自分の prototype プロパティを設定してるんだと思います

function* 関数を実行して作られるもの

いろいろあって複雑になってきましたが

● function* (){} で GeneratorFunction を作れます
特殊なことしないとアクセス出来ない GeneratorFuncton 関数を new して作ることも出来ます
Function 関数と同じようなものです

● 作った GeneratorFunction (のインスタンス) を実行して generator を作れます
(function*(){})()
これの next メソッドを呼ぶと GeneratorFunction 内の yield 単位で実行されます

generator オブジェクトの中身を見るとこんな構造です
Object
__proto__: Object
__proto__: Object
constructor: GeneratorFunction
next: function next()
throw: function throw()
Symbol(Symbol.toStringTag): "Generator"
__proto__: Object
[[GeneratorStatus]]: "closed"
[[GeneratorFunction]]: function ()
[[GeneratorReceiver]]: Window

[[]] という特殊プロパティなのがありますね
これはアクセスの仕方がわからなかったです
できるのかもわかってません
Symbol 関係?

[[GeneratorStatus]] には suspend や closed など状態が入っています




ちょっと書くつもりが気になったのを調べたり試してるとすごく長くなってしまいました
単純に思っていた yield ですがこれもまた 奥が深そうです