JavaScript
CreateJS

javascriptで再帰関数っぽいもの書いたらUncaught RangeErrorが出てきた

More than 1 year has passed since last update.

再帰関数を使う前にやったこと

create.jsを使って背景に雲を流そう!
ループにしてずっとちょくちょく流れてくるようにしよう!
ということで以下のように書いてみた。

function cloudLoop() {
  var _mc = new lib.cloud;
  var mcNum = 0;
  var posY = Math.random() * (50 + 1 + 100) - 100;

  _mc.gotoAndStop(mcNum);
  _mc.alpha = 0.8;
  sceneContainer.cloudPos.addChild(_mc);

  createjs.Tween.get(_mc, {loop: true})
    .to({ x: -840 }, 6000)
    .to({ x: 0 }, 0)
    .call(function(){ posY = Math.random() * (50 + 1 + 100) - 100 })
    .to({ y: posY }, 0)
    .call(function(){ mcNum = (mcNum === 0) ? 1 : 0 })
    .call(function(){ _mc.gotoAndStop(mcNum) })
    .wait(Math.random() * 1500);
}

雲が流れて流れきったら元の位置に戻ってきて、別の雲の画像に切り替わってY座標を変えてちょっと待ってから...
をループしてくれるはずだった。

残念なことに結果は雲は流れて別の雲の画像に切り替わって、がループするだけでY座標は変わってくれないし流れた後に待ってくれないままひたすら流れてくる。
原因はページを読み込んだ時にcreatejs内の数字は決まっちゃってるということらしくてガバjsのくせになんだこいつ、という感じ。

再帰関数を使ってみる

javascriptは触り始めて間もないので再帰関数ってreturnのとこでその関数呼び出せばええんやろ!楽勝やんけ!と意気込んで書いたコードが以下の通り。

function cloudLoop() {
  var waitTime = Math.floor(Math.random() * 1500);
  var posY = Math.floor(Math.random() * (50 + 1 +100) - 100);
  var mcNum = 0;
  var cloudMc = new lib.cloud;

  cloudMc.gotoAndStop(mcNum);
  cloudMc.alpha = 0.8;
  sceneContainer.cloudPos.addChild(cloudMc);

  createjs.Tween.get(cloudMc)
    .wait(waitTime)
    .to({x: -840}, 6000)
    .to({x: 0}, 0)
    .to({y: posY}, 0)
    .call(function(){mcNum = (mcNum === 0) ? 1 : 0});

  return cloudLoop();
}

Uncaught RangeError登場

結果がこちら

Uncaught RangeError: Maximum call stack size exceeded

はい、よくわからないです。
ということでgoogle先生に相談してみる。
するとこんな記事が出てきた。

【今日のバグ取り】 JavaScript でコールスタックが溢れていたのをどうにかした話 - 無駄と文化

なるほどコールスタックがいっぱいになってるからとりあえずスタックトレース辿ってエラーの原因探したらええんやな。
何言ってるかわかりません。横文字やめてください。
わからないのでまたgoogle先生に相談。

コールスタック

プログラムのデバッグ時に関数の呼び出しの階層状態を表示する機能。

スタックトレース

エラーが発生したときに表示される内容で、そのエラーが発生するまでの過程を表示したもの(consoleのあの赤いとこ)

コールスタックは正直まだ何言ってるかわからないけど、コンソールのあの赤いやつの名前がスタックトレースだと知って満足したので次へ。

で、さっきのサイトを読み進めていくとご丁寧にコールスタックの説明が書いてあった。
しかもわかりやすい。

JavaScript のプログラム中で関数が実行されると、関数が実行された場所やそのときの状況などとの情報が コールスタック と呼ばれるメモリ領域に積まれます。

なんかここまで書いておいて思ったことはさっきのリンク先のサイト読めばいいじゃん。めっちゃわかりやすいし。
ということで今回のエラーをどう対処したか、でこの記事は終わり。

解決

setIntervalで回せばそれで解決ですが、他の場所でsetInterval使ってしかも中身がいっぱいなので今回はなんとか再帰関数を使って解決したい。

今回の場合は値を返すわけではないので

  return cloudLoop();

がだめっぽい。
ということで関数の呼び出しをcreatejs内に書いてしまう。

で、書いたコードがこちら。

var mcNum = 0;

function cloudLoop() {
  if(Flag.isHit || Flag.isDrop) return false;
  var waitTime = Math.random() * 1500;
  var posY = Math.random() * (50 + 1 +100) - 100;
  var cloudMc = new lib.cloud;

  cloudMc.gotoAndStop(mcNum);
  cloudMc.alpha = 0.8;
  sceneContainer.cloudPos.addChild(cloudMc);

  createjs.Tween.get(cloudMc)
    .to({y: posY}, 0) // ここがまずかった
    .wait(waitTime)
    .to({x: -840}, 6000)
    .call(function(){
      mcNum = (mcNum === 0) ? 1 : 0;
      cloudLoop(); // 関数の呼び出しをここに移動
      sceneContainer.cloudPos.removeChild(cloudMc); // これを忘れてた
    });
}

とりあえずcreatejs内で関数を呼び出したらうまいこと動いてくれた。
でも雲の位置がうまく上下に変化してくれない。
なんでだ…と30分くらい考えて気づいたのがそもそも左に雲が移動した後に上下の位置を変えて、その変わったやつはもう動いてくれないからそりゃ上下の位置変わらんわ。
ということで横移動の前に上下の位置を決めてくれるように順番を変更。
あと、作り出したアドしたやつがずっと残ってるのも気持ち悪いので最後に削除。
これでとりあえず思い通りの動きをしてくれた。

今回の教訓

  • 再帰関数を舐めてはいけない。
  • ちゃんと順番を考えてコードを書く。
  • 先輩に聞けばだいたい解決する。

以上の3つでした。