プロトタイプチェーンを意識したJavaScriptを考える

nishigoJavaScript,PHP,prototype.js

今年のワールドカップはドイツが優勝すると信じてやまないnishigoです。

最近は開発でもっぱらJavaScriptしか触っていません。
普段はPHPでコードを書くことのほうが多いので、私のようにJavaScriptは
どうしてもprototype.js等のライブラリに頼りきってしまう、
なんて方も多いのではないでしょうか。

そんな苦手意識を少しでも克服する為に、
あまり取り上げられることのないコード統一化TipsをJavaScriptの特性に触れながら
解説していきます。

Array Object
PHPerであれば、PHPの豊富な組み込み関数をJavaScriptでも気兼ねなく使いたいと
思ったことのある方はいるでしょう。
php.jsではそんなPHPでよく利用する組み込み関数を多数メソッドとして定義して
くれています。
http://phpjs.org

そしてこのphp.jsが定義している各メソッドは非常に厳密であり、
ソースを読むことでJavaScriptの言語としての特性を理解する手助けをしてくれます。

例で、is_array()のソースを読んでいきましょう。
var is_array = function(arr) {
  return typeof arr === 'object' &&
    typeof arr.length === 'number' &&
    !(arr.propertyIsEnumerable('length')) &&
    typeof arr.splice === 'function';
};

このreturn文の一行目では、引数arrが’object’であることを確認し、
ここでオブジェクトか配列、もしくはnullであるかを確認しています。
二行目では引数のlengthプロパティに数値が割り当てられてるかの確認。
三行目にはlengthプロパティがfor in文で回せるかの確認。
そして最後に配列の特性であるspliceメソッドを持ち得ているかの確認です。

このようにJavaScriptの配列はtypeof演算子でも’object’と認識してしまう程
分かりにくいものとなっていて、他のプログラミング言語と比べて配列とオブジェクト
の使い分けが難しいところでもあります。

もし対象となるマッパーオブジェクトが短いプロパティ名や
数値のみで構成されていて、それらをループで回したいのであれば、
思い切って二次元配列にしてしまいfor文でループしたほうが良い場合もあります。

PHPであれば下記のようにforeach文で回せば済む話でしょう。
foreach ($values as $key => $value) {
  // done
}

foreach文に変わるものとしてJavaScriptではfor in文を思い浮かべるかもしれませんが、
JavaScriptの配列はあくまでオブジェクトなので、すべてのプロパティを列挙します。
つまり、プロトタイプチェーン上のプロパティすら列挙してしまい、余計なプロパティまで
列挙される処理はあまり好ましくありません。

可能であればfor文で記述したほうが良いでしょう。
var techMembers = [ ['nishigo1', 0], ['nishigo2', 2], .. ];
var i;
for (i = 0; i < techMembers.length; i += 1) {
  var post = techMembers[1] === 0 ? 'staff' : 'manager';
  document.writeln(post + 'の' + techMembers[0] + 'です');
}

配列となると、まず空の配列を作りそこに値を代入もしくは格納していくという
処理を書くこともあるでしょう。
// PHP_code
$data = array();
foreach ($foo as $value) {
  $data[] = $value;
}

JavaScriptでは空の配列オブジェクト(厳密にはArray.prototypeがある為空ではありませんが)
を定義する際にはリテラル記法で記述しましょう。
var techMembers = []; // ○
var techMembers = new Array(); // ×

なぜならnew 演算子でのオブジェクト定義はvalueOfメソッドを持ってしまっているからです。
ラップされた値を返すvalueOfメソッドを使う機会は皆無であるので、
new Arrayやnew Object の利用はすべきではありません。

(また、new演算子を使用するオブジェクトは一文字目が大文字であるべきと
いう暗黙のルールもあります。)

Function Object
JavaScriptのfunction文は、どこに書かれていようとも該当スコープの
先頭に移動させられる性質を持っています。
これによって関数をスコープ内であればどこからでも呼び出せますが、
省略記法でなくvar文で記述(function式)することをオススメします。
(無論グローバル関数を置かないことを前提として)
var foo = function () { };
なぜならJavaScriptを書いているとクロージャのように変数に格納していく
機会のほうが圧倒的に多いであろうからです。
var addSelectMember = function(members, func) {
  var lastMember = 0;
  if (is_array(members) && typeof func === 'function') {
    var addEvent = function(post) {
      var idName = post === 2 ? 'manager' : 'staff';
      document.getElementById(idName).setAttibute('onClick', func(this));
    };
    var i;
    for (i = 0; i < members.length; i += 1) {
      if (lastMember < members[i]) lastMember = i;
      addEvent.call(this, members[i]);
    }
  }
  return lastMember;
} (/* Array Object, Function Object */);

function文で統一されたコードの方が前文で記述されることで
難しい処理もある程度は可読性があがります。
PHPでは5.3から匿名関数が使用できますが、
5.2以前で開発を行っている方は多少不便に感じるかもしれません。

しかし実はなんてことはない、
普段JavaScriptでパブリックメソッドを記述する方法と大差ないでしょう。
var foo = {
  setMaxLength : function() {
    // done
  },
  ....
};
foo.moo = function() { };

そして、JavaScriptと言ったら避けては通れないIEの数々の壁(?)ですが、
そんなJScriptエンジンさんでも気に入ってる部分があるのでご紹介 :->

1つ目は、オブジェクトプロパティに次の要素が無いのにカンマをつけると
IEではランタイムエラーが発生する点。
hogeObject = {
  textAlign : 'left',
  font : '15px',
  color : '#FFF',  // Syntax error ;-<
};

こういったところをFirefoxは無視してしまうので後々IEで
ブラウザチェックするという場面で戸惑うことも多いですね。
この部分に関してのみ言えばIEが構文チェックに厳密な点もあると言えるでしょう。

2つ目は、IE6,7ではsetAttribute(もしくはgetAttribute)での属性名はlowerCamelで記述する必要がある点。
element.setAttribute("maxlength", 5); => element.setAttribute("maxLength", 5);
prototype.jsではEvent.observe()のイベント名で’mousedown’のように記述しますが、
上記のようにlowerCamel記法でJavaScriptらしい記述に統一できればな、と個人的には思います。

と、IEについてはこれ以上書くことが見つかりません。。。
IE9でのChockraエンジンに期待を寄せつつJavaScriptの未来に期待したいと思います。

最後に全体を通しての細かい点ですが、ホワイトスペースや括弧の記述なども
開発者にとっては気になるところかもしれません。

数あるJavaScriptのライブラリでも、if文に{}がついてないことはよくあります。
しかしそのような場合は{}内の処理を同じ行にまとめ、そして2つ以上命令文を書かないの
が通例です。

そしてifやswitch文等の括弧とのホワイトスペースは関数呼び出しと区別するために
つけたほうがよいかもしれません。
if(typeof foo === 'object'){ /* */ };  //ホワイトスペースなし
if (typeof foo === 'function') {
  // ホワイトスペースあり
};
Element.hasAttribute();  // FunctionObject

今回取り上げる項目は以上ですが、変数やメソッドの命名規則だけでなく
こうした細かいコード記述理論も開発チーム内で統一・共有されると、
より美しいコードが生まれてくるかもしれません。

nishigoJavaScript,PHP,prototype.js

Posted by nishigo