初級者なりにJSを書く際に気をつけてること

最近疲れがとれなくて土曜日にラクシュミに行くのがつらくなってきたアライです。

社内のFlashクリエイターがFlashの動きだけを見てそれをトレースしたコードを書く勉強会をやっていて、その発表会に混ぜて貰ったので僕はJSでCanvasバージョンとDOMバージョンを実装してみました。
そのコードの発表とともに僕が普段気を気をつけているコーディングスタイルを披露したいと思います!(大抵他人の受け売りです)

オレオレコーディングスタイル

僕だけじゃなく同僚のみなさんもJS実装なさってて、コードを見たら自分と違っておもしろいなあと感じました。
以下に僕がJSを書くときに気をつけてることを列挙します。

  • 変数は先頭でまとめて宣言する!
  • 基本的に同値演算子「===」を使う!!
  • 分岐は条件の個数によって使い分ける!!!
  • ループは名前の参照を極力減らす!!!!
  • jQueryで要素を取得する場合、第二引数を指定する!!!!!
  • 1つのファイルで完結するJSは(function(){}());でラップする!!!!!!

上記をふまえつつコードを見ていきましょう。。

Canvas版

いろいろ拡張性を持たせるために模索中です。まだ使ってない変数も定義しているなど完成度という点ではまだまだ低いです…。
このスクリプトではまだ応用は利かないです。抽象化って難しいです。。

(function() {
  // 設定
  var canvas = document.getElementById('canvas'),
      ctx = canvas.getContext('2d'),
      cw = 700, // canvas width キャンバスの横幅
      ch = 400, // canvas height キャンバスの縦幅
      x = r / 2,
      y = ch / 2,
      n = 10, // number 個数
      r = cw/(n-1)/2, // radius 半径
      m, // margin 余白
      fps = 60,
      sp = 3, //speed
      interval = 1000/fps,
      al = [], // arc list 円弧リストを挿入する配列
      nt, // normal timer ノーマルのタイマーID
      rt, // reverse timer リバースのタイマーID
      st, // state 状態を示す変数 ノーマルorリバース
      init,
      process,
      normal,
      reverse,
      drawArc,
      Arc;

  // 初期化
  init = function() {
    canvas.width = cw;
    canvas.height = ch;
    canvas.style.marginTop = -ch/2+'px';
    canvas.style.marginLeft = -cw/2+'px';
    // 俺式ループ(jQueryクックブックかなにかの本に書いてあった…)
    // セミコロンを書き忘れた時のエラーがわかりづらいから使わない方が良さそう
    for(var i=-1;++i<n;) {
      al[i] = new Arc(i);
      drawArc(i);
    }
    normal();
  };

  process = function() {
    // canvasの大きさと同じ矩形を描いてそれまでに描いたものを消しているように見せかけている…
    // ctx.fillStyle = '#fff';
    // ctx.fillRect(0,0,cw,ch);
    ctx.clearRect(0,0,cw,ch);
    if (st === 'n') {
      for (var i=-1;++i<n;) {
        // canvasの左端から完全にはみ出すまで3pxずつ左に移動する
        // はみ出したら右端の見えないぎりぎりの位置に移動
        al[i].x = (al[i].x >= -r + sp) ? al[i].x - sp : cw + r;
        drawArc(i);
      }
    } else if (st === 'r') {
      for (var i=-1;++i<n;) {
        al[i].x = (al[i].x <= cw + r - sp) ? al[i].x + sp : -r;
        drawArc(i);
      }
    }
  };

  // ロード時、マウスアウト時の処理
  normal = function() {
    st = 'n';
    // rtがtrueならreverseの処理をクリア
    if (rt) clearInterval(rt);
    nt = setInterval(process, interval);
  }

  // canvas要素をマウスオーバーした時の処理
  reverse = function() {
    st = 'r';
    // ntがtrueならnormalの処理をクリア
    if (nt) clearInterval(nt);
    rt = setInterval(process, interval);
  }

  drawArc = function(i) {
    ctx.beginPath();
    ctx.arc(al[i].x, al[i].y, al[i].r, 0, Math.PI*2, false);
    ctx.fillStyle = al[i].color;
    ctx.fill();
  }

  // 円弧クラス
  Arc = function(i) {
    this.x = r*(i*2+1) //x*(i+1) + r * (i*2+1);
    this.y = y;
    this.r = r;
    this.color = 'rgba('+Math.floor(Math.random()*255)+','+Math.floor(Math.random()*255)+','+Math.floor(Math.random()*255)+', .'+(Math.floor(Math.random()*9)+1)+')';
    return this;
  }

  // イベント登録
  window.addEventListener('load', init, false);
  canvas.addEventListener('mouseenter', reverse, false);
  canvas.addEventListener('mouseleave', normal, false);
}());

Canvasを避けて通っていたので、今回初めて触りました。
円を移動させるために一回一回Canvasに白を塗り重ねていく作業が必要なのがDOMしか触ったことがない自分にとっては新鮮でした。
移動速度と距離で端数が出たら間隔がずれて重なってしまうので、そこを揃える計算がなかなか難しいです…

DOM版(jQuery1.8.2使用)

こちらは我々マークアップエンジニアに馴染みが深いDOMバージョンです。
DOMの操作はjQueryが楽でいいです。
jQueryのアニメーションメソッドを使わない制約の下コーディングしています。

(function() {
  var $li = $('li', '#contents'),               // ループさせる対象
      $canvas = $li.parent(),                   // 箱
      n,                                        // ループさせる対象の個数
      f = 50,                                   // フレーム
      x = 0,                                    // 1つめのx座標の位置
      w = 200,                                  // 円の幅
      h = 200,                                  // 円の高さ
      sp = 2,                                   // 移動するスピード
      interval = 1000 / f,                      // 繰り返しの時間
      nt,                                       // ノーマルのタイマーID
      rt,                                       // リバースのタイマーID
      cw = parseInt($canvas.css('width'), 10),  // 箱の幅
      ch = parseInt($canvas.css('height'), 10), // 箱の高さ
      st,                                       // 状態
      init,                                     // 初期化関数
      normal,                                   // ノーマル時の処理関数
      reverse,                                  // リバース時の処理関数
      process;                                  // ループさせる要素を動かす処理関数

  init = function() {
    $canvas.append($li.clone());
    $li = $('li', '#contents');
    n = $li.length;
    for (var i=-1;++i<n;) {
      $($li[i]).css({
        'margin-top': -w/2,
        left: w*i,
        width: w,
        height: h
      });
    }
    normal();
  }

  normal = function() {
    st = 'n';
    if (rt) clearInterval(rt);
    nt = setInterval(process, interval);
  }

  reverse = function() {
    st = 'r';
    if (nt) clearInterval(nt);
    rt = setInterval(process, interval);
  }

  process = function() {
    if (st === 'n') {
      for (var i=-1;++i<n;) {
        x = parseInt($($li[i]).css('left'), 10);
        $($li[i]).css('left', (x <= -w) ? w*n-w-sp : x-sp +'px');
      }
    } else if (st === 'r') {
      for (var i=-1;++i<n;) {
        x = parseInt($($li[i]).css('left'), 10);
        $($li[i]).css('left', (x >= w*n-w) ? -w+sp : x+sp +'px');
      }
    }
  }

  // イベント
  $(window).on('load', init);
  $canvas.on({
    'mouseenter' : reverse,
    'mouseleave' : normal
  });
}());

jQueryはアップデートされる毎にメソッドが変わるので追い切れてないです…。
今回はonメソッドでイベントを設定しようと思って、loadもonで実装しましたが、どうするのが一番良かったのかよくわかってません。

今後の課題

  • OOPっぽく書けるようになってきたけど完全に落とし込めてない
  • 使ったことがない文が多すぎる(switchとかcatchとか…
  • 知らないメソッドが多すぎる
  • そもそもJSもjQueryもなんとなく使ってるに過ぎない…

帰宅してからも机に向かって毎日こつこつと勉強していくに限ります。
本を読むだけでは全然理解できないので、書きつつ本を読みつつ…最悪少しずつでいいから前進していかないとダメだなと痛感しました…。