業務中ワイ
ワイ「お、また株式会社ブラックはんからお仕事依頼のメールが来てるで」
ワイ「どれどれ・・・」
暗井「お世話になっております。株式会社ブラックの暗井 暗人(くらい・あんと)です」
暗井「クリックするとフワフワっとだんだん縦横に大きくなるボックスを作ってください」
暗井「予算は800万円です」
ワイ「これまた何ちゅうフワフワっとした指示や」
ワイ「でもjQueryの**animate
**メソッド使えば余裕やろ」
ワイ「かしこまりました」
ワイ「何とか800万円以内に抑えます」
ワイ「送信っと」
ワイ「お、早速お返事や」
暗井「なお、発注元の都合でjQueryの使用はできません」
ワイ「ファッ!?」
ワイ「まじか・・・jQuery禁止か」
ワイ「800万円で出来るかな・・・」
社長「(それは出来るやろ・・・何年かける気や・・・)」
ザコーダーワイ、jQuery禁止で困る
ワイ「jQueryなしか・・・」
ワイ「jQueryのanimate
みたいなこと、素のJSとCSSだけで出来んのかな・・・」
ハスケル子「できますよ」
ハスケル子「今回の仕様ならジェネレーター関数なんて良いんじゃないですか」
ワイ「何やっけそれ」
ジェネレーター関数とは
ハスケル子「ジェネレーター関数っていうのは」
ハスケル子「実行すると、ジェネレーターを返す関数です」
ワイ「そのジェネレーターが分からんわ」
ハスケル子「ジェネレーターは、ある種のイテレーターです」
ワイ「今度はそのイテレーターが分からん。。。」
ハスケル子「・・・」
ハスケル子「簡単にいうと、関数の途中で一旦止まれるんです」
ハスケル子「具体的にはyield
って書くと、そこで関数の処理が一旦止まります」
やってみよう
ハスケル子「じゃあ実際やってみますね」
function* geneFunc () {
console.log("1回目");
yield;
console.log("2回目");
yield;
console.log("3回目");
yield;
console.log("4回目");
yield;
console.log("最後");
}
ハスケル子「↑こんな感じでfunction
に*
をつけるとジェネレーター関数になります」
ハスケル子「yield
が来るたびに処理が一旦止まって関数の外の世界に出るので」
ハスケル子「この関数は合計5回に分けて実行することができるんです。」
ワイ「ほえ〜」
ハスケル子「まず、geneFunc
を実行して、戻り値であるジェネレーターを受け取ります」
const generator = geneFunc();
ワイ「この時点でconsole.log("1回目");
が実行される感じ?」
ハスケル子「いえ、まだです」
ハスケル子「geneFunc
を実行した時点では、関数の中の処理は何も実行されません」
ハスケル子「↓こうするとconsole.log("1回目");
が実行されます」
generator.next();
ワイ「なるほどな」
ワイ「generator
の持ってるnext
メソッドを実行すると」
ワイ「yield
の所まで実行して、一旦止まってくれんねやな」
ハスケル子「はい」
ハスケル子「一旦止まって、関数の外で何か処理をして」
ハスケル子「またgenerator.next()
で関数の中に戻ったりできるわけです」
ワイ「おお〜、ほな今回の例やとgenerator.next()
を5回実行すると」
ワイ「geneFunc
の中の処理を全部やり切ってくれるわけやね」
ハスケル子「そうです」
ハスケル子「慣れないうちはちょっと、いつどこで何をやってるのか分かりにくいかもしれませんけど」
ハスケル子「処理の順番とかタイミングを自在にコントロールできる強力な機能です」
ハスケル子「非同期処理と組み合わせて使うこともできて」
ハスケル子「redux-sagaなんかは」
ワイらは
async/await
やなくてジェネレーター関数を使っていくんや!
ハスケル子「って表明してたりします」
ワイ「おお〜・・・」
そんで、どうやってフワフワっとさせんの?
ワイ「そんでハスケル子ちゃん」
ワイ「クリックするとフワフワっとだんだん縦横に大きくなるボックス・・・」
ワイ「これとジェネレータ、関係あるん・・・?」
ハスケル子「大ありです」
ハスケル子「ジェネレータ関数を使うとだいぶスッキリ書けますよ」
ハスケル子「まずは・・・」
const box = document.querySelector(".box");
ハスケル子「↑大きさを操作する対象となるDOM要素を取得します」
ハスケル子「そして・・・」
function* fuwafuwa () {
let boxHeight = 30; // デフォルトの高さは30px
let boxWidth = 30; // デフォルトの横幅は30px
boxHeight += 30;
box.style.height = boxHeight + "px"
// boxを30px高くする
yield;
// 一旦止まる
boxWidth += 30;
box.style.width = boxWidth + "px"
// boxを30px横長にする
yield;
// 一旦止まる
}
ハスケル子「↑fuwafuwa
っていうジェネレータ関数を定義します」
ハスケル子「この関数の中の処理は・・・」
ワイ「box
を30px
高くしてyield
で止まる」
ワイ「box
を30px
横長にしてyield
で止まる」
ワイ「って感じやね」
ハスケル子「そうです」
ハスケル子「この処理を関数の中にいっぱい書きます」
ワイ「なるほど!」
ワイ「↓こんな感じやな・・・!」
boxHeight += 30;
box.style.height = boxHeight + "px"
// boxを30px高くする
yield;
// 一旦止まる
boxWidth += 30;
box.style.width = boxWidth + "px"
// boxを30px横長にする
yield;
// 一旦止まる
boxHeight += 30;
box.style.height = boxHeight + "px"
// boxを30px高くする
yield;
// 一旦止まる
boxWidth += 30;
box.style.width = boxWidth + "px"
// boxを30px横長にする
yield;
// 一旦止まる
boxHeight += 30;
box.style.height = boxHeight + "px"
// boxを30px高くする
yield;
// 一旦止まる
boxWidth += 30;
box.style.width = boxWidth + "px"
// boxを30px横長にする
yield;
// 一旦止まる
ハスケル子「・・・」
ハスケル子「まあ、それでもいいんですけど」
ハスケル子「while
文使いましょう・・・」
while (boxHeight < 300) {
boxHeight += 30;
box.style.height = boxHeight + "px"
yield;
boxWidth += 30;
box.style.width = boxWidth + "px"
yield;
}
ハスケル子「こうしておけば」
ハスケル子「boxの高さが300px
になるまで繰り返してくれますよ」
ワイ「おお、ループの中でもyield
を使えるんやね」
関数をチョイチョイ止めながら実行できるようになった
ワイ「でも、これでクリックするとフワフワっと大きくなるボックス作れる・・・?」
ハスケル子「はい、ここでtransitionend
イベントを使うんです」
ワイ「transitionend
イベント・・・もしかして」
ワイ「transition
によるCSSプロパティの変化が終わった時に発火されるイベントかいな・・・!?」
ハスケル子「はい」
ワイ「おお、そんな便利なイベントが。。。」
ハスケル子「そのイベントと、ジェネレーターを組み合わせるんです」
const generator = fuwafuwa();
ハスケル子「↑まずはfuwafuwa
関数を実行してジェネレータを受け取ります」
box.addEventListener("transitionend", () => generator.next());
ハスケル子「↑そして、box
のtransition
が終了するたびに」
ハスケル子「generator.next()
が実行されるように設定しておくんです」
ワイ「なるほどな」
ワイ「transition
による動きが終了するたびに、イベントが発火してgenerator.next()
が実行されて」
ワイ「それによって次のtransition
が始まり」
ワイ「またそのtransition
の動きが終わることでイベントが発火して・・・」
ワイ「って相互再帰してる感じなんやな」
ハスケル子「そうです」
ハスケル子「あとは、はじめにbox
をクリックした時にもgenerator.next()
が実行されるように」
ハスケル子「イベントリスナを登録しておきます」
box.addEventListener("click", () => generator.next());
ハスケル子「↑こうですね」
ハスケル子「実際に動かしてみると↓こんな感じです」
See the Pen fuwafuwa generator by Yametaro (@Yametaro) on CodePen.
ワイ「おお、フワフワっと大きくなってる!」
ワイ「ジェネレーターを活用すると、関数内の処理のタイミングを自在にコントロールできんねやな・・・!」
ワイ「ありがとう、ハスケル子ちゃん」
ワイ「お礼にお小遣い400万円あげるわ!」
社長「(なに売り上げを半分にしてんねん・・・!)」
〜おしまい〜
※登場人物、価格設定等はフィクションです。
追記1
ジェネレーター関数について学ぶなら↓こちらのサイトがおすすめです。
JavaScript初級者から中級者になろう 十六章第六回 ジェネレータ
追記2
IEも対応する場合は、Babel使ってな!
追記3 新しい記事を書いたで!
↓こちらの記事もよろしくやで!
すごいHaskell、ハスケル子と学ぼう!