JavaScript

【JavaScript】必須パターン(メモ)

勉強用メモ
編集途中
ES3,5をもとに書いてあります

はじめに

バグが見つかったとき、問題把握と解決のための時間がかかる。かかわったプロジェクトの規模が大きければ大きいほどバグの修正、バグの作成、バグの発見が違う人であるという問題が発生し、解決のための時間の短縮が課題となる。
さらに、コードは書くよりも読むことに時間がかかるため、保守しやすいコードを書くことがとても重要になる。
保守しやすいコードとは以下のようなコードである。

  • 読みやすい
  • 一貫性がある
  • 見通しが良い
  • 1人で書いたようなコードに見える
  • ドキュメントが整備されている

グローバル変数の使用を最小にする

JavaScriptは関数を使ってスコープを管理する。関数の内部で宣言された変数はその関数においてローカルであり、その関数の外部からは利用できない。一方で、グローバル変数は関数の外部で宣言されたか、宣言されることなく使われる。

グローバル変数を使うときの問題

グローバル変数の問題は、JavaScriptアプリケーション、ウェブページにあるすべてのコードで共有されてしまうこと。それによって変数名が衝突する危険性がある。ウェブページでは、自作ではないコードを取り込む場合がある。

  • サードパーティ製のJavaScriptライブラリ
  • 広告パートナーが使用するスクリプト
  • ユーザの行動を追跡し分析するサードパーティ製のスクリプト
  • ウィジェット、バッジ、ボタン

こういったものにより、定義した変数が前に定義されているものと衝突し、スクリプトが停止してしまう可能性がある。だから、グローバル変数はできる限り使わないようにする必要がある。グローバル変数をできる限り減らすために重要なパターンは、常にvarを使って変数を宣言することである。

しかし、JavaScriptの2つの機能によって意図していないグローバル変数が作られてしまうことがある。

  • 変数を宣言していなくても変数が使えてしまう点。
  • JavaScriptには暗黙のグローバルという概念がある点。
// よくないパターン
function sum(x, y) {
  result = x + y; // resultという名前のグローバル変数があることになってしまう
  return result;
}
// 直したやつ
function sum(x, y) {
  var result = x + y; // varをつけることでresultをローカル変数に変更
  return result;
}

// 暗黙のグローバルが作られるもうひとつのパターン
function foo() {
  var a = b = 0; // aはvarによって宣言されているがbは宣言されていない
  // ...
}
// 上のやつを直したやつ
function foo() {
  var a, b;
  a = b = 0;
  // ...
}

varを忘れると...

明示的に定義されたグローバルと暗黙のグローバルには少し違いがある。
delete演算子を使って変数を未定義に出来るかどうかの違いである。

  • varを使って関数の外部で作られたグローバルは削除できない。
  • 関数の内外問わずvarを使わずに作られた暗黙のグローバルは削除できる。

これによって暗黙のグローバルは技術的には、変数ではなく、グローバルオブジェクトのプロパティであることがわかる。プロパティはdeleteで削除できるが変数は削除できない。

グローバルオブジェクトへのアクセス

ブラウザ環境では、グローバルオブジェクトはwindowプロパティを介してコードのどこからでもアクセスできる。しかし、このプロパティは他の箇所で呼ばれる可能性がある。識別子windowをハードコーディングせずにグローバルオブジェクトにアクセスする必要があるときは、以下のようにすれば、入れ子になった関数のどこからでも実行できる。

var global = (function() {
  return this;
}()};

newを使ったコンストラクタではなく関数として呼び出された関数の内部では、thisは必ずグローバルオブジェクトを指すからである。しかし、ECMAScript 5のstrictモードでは成立しないため、strictモードに対応させるためには、別のパターンを採用しなければならない。
例えば、ライブラリを開発しているのであれば、そのライブラリのコードを即時関数で包むことができる。グローバルスコープからは、その即時関数にthisへの参照をパラメータとして渡せばいいだろう。

単独varパターン

関数の先頭でvar文をひとつにまとめるパターンには以下の利点がある。

  • その関数で必要なすべてのローカル変数が一目でわかるように1か所にまとめてある。
  • 変数を定義する前にその変数を使用してしまう論理上のエラーを予防できる。
  • 宣言する変数を覚えるのに役立ち、グローバルを最小にできる。
  • コードが少なくて済む

記述例は以下の通り。

function func() {
  var a = 1,
      b = 2,
      sum = a + b,
      myobject = {},
      i,
      j;
  // 関数の中身...
}

このようにvarの1文を使って複数の変数を宣言するときは、変数をカンマで区切る。また、変数宣言の際に初期値で変数を初期化することで論理上のエラーの予防にもなる。
DOMの参照を使って処理することもできる。以下のコードのように、DOM参照をローカル変数に代入する処理を一つの宣言文にまとめることができる

function updateElement() {
  var el = document.getElementById("result"),
      style = el.style;
}

巻き上げ

JavaScriptでは、ひとつの関数の内部のどこにでもvar文を書くことができる。これらの変数はその関数の先頭で宣言されたのと同じように動作する。これを巻き上げ(hoisting)と呼ぶ。変数を宣言する前にその変数を使っている場合、論理上のエラーになるはずが、JavaScriptでは同じスコープ(関数)の中にある限り、その変数は宣言済みとして扱われる。

name = "global"; // グローバル変数
function func() {
  // var name; ここで宣言されたことになる
  alert(name); // "undefined" // 関数内でnameが宣言されたことになっているためグローバル変数のnameではなく宣言されただけのnameが呼ばれる
  var name = "local";
  alert(name); // "local"
}

parseInt()による数値変換

parseInt()を使うと文字列を数値に変換できる。この関数は第2パラメータに基数を指定できるが、省略される場合が多い。
ECMAScript 3では先頭が0で始まる文字列は8進数として扱われるが、ES5ではこの仕様が変更された。これによって予想していない問題が発生する可能性があるため、基数は指定するようにする。

var month = "06",
    year = "09";
month = parseInt(month, 10);
year = parseInt(year, 10);

ここで基数を省略すると、"09"は8進数で解釈される。そのため、09は8進数として妥当ではないため、0が戻り値になる。
文字列を数値に変換する方法は他にもある。

+"08" // 8
Number("08") // 8

これらの方法はparseInt()を使うより早くなることがある。また、parseInt()は"08 hello"のような入力に対しては数値を返すが、それ以外の場合はNaNを返す。

命名の作法

コンストラクタの頭文字は大文字にする

JavaScriptにはクラスはないが、newで呼ばれるコンストラクタ関数がある。
ある関数がコンストラクタとしてふるまうのか、通常の関数としてふるまうのか、頭文字を大文字にすることで判別する。newで呼ばれることを想定しない関数やメソッドについては頭文字を小文字にする。

function MyConstructor() {...} // コンストラクタ
function myFunction() {...} // 通常の関数

単語でわける

変数名や関数名が複数の単語で構成されている場合、英文と同じように単語を区切る作法に従うほうがよい。一般的なものとしては、キャメルケースを使うやり方がある。キャメルケースの作法では、各単語の頭文字だけを大文字にして、それ以外の文字は小文字にする。
コンストラクタの名前についてはmyConstructor()のように大文字キャメルケースを、関数やメソッドについてはmyFunction()、calclateArea()、getFirstName()のように小文字キャメルケースを使う。
変数については、通常小文字キャメルケースが使われる。また、すべて小文字にして単語の区切りにアンダースコアをつける方法もある。これを使えば、関数なのか、プリミティブやオブジェクトの識別子なのか、区別するのが楽になる。

その他

命名の作法を使って言語の機能を補ったり、置き換える場合がある。
例えば、JavaScriptには定数を定義する方法がない。このため値を変更すべきでない変数にはすべて大文字を使う作法が採用されている。

// こんな感じで全部大文字
var PI = 3.14,
    MAX_WIDTH = 800;

定数と競合してしまうが、グローバル変数の名前をすべて大文字にする作法もある。すべて大文字にすることで、グローバル変数を減らすことに効果的で、一目でそれだとわかるという利点もある。

欠けている機能を模倣した例として、プライベートメンバの作法がある。プライベートのメソッドやプロパティの先頭にアンダースコアを付加することで、一目でわかるようにしている。

var person = {
    getName: function() {
        return this._getFirst() + ' ' + this._getLast();
    },
    _getFirst: function() {
        // ...
    },
    _getLast: function() {
        // ...
    }
};

getName()はパブリックメソッドを意図していて、安定したAPIの一部であるのに対して、_getFirst(),_getLast()はプライベートメソッドを意図している。どれも通常のパブリックメソッドではあるが、アンスコで前置きすることで、personオブジェクトを使うとき、これらのメソッドは次のリリースでの動作は保証されていないこと、そのため直接使うべきでないことを警告する効果がある。
JSLintはアンスコが前置きされると苦情を言ってくるが、オプション nomen: falseに設定すると黙る。

以下は_privateの作法のバリエーション

  • アンダースコアが後起きされていればプライベートの意味(name_、getElements_()など)
  • アンダースコアを1個前置きしたらprotectedプロパティ、2個前置きしたら_privateプロパティ
  • Firefoxでは言語の一部ではないがいくつかの内部プロパティを利用できる。これらのプロパティはアンダースコア2個が前後に置かれている(protoparentなど)

コメントを書く

作業中は問題ないが、少し間を空けて作業を再開したり、ほかの人が読んだりする場合、コメントがないと解読に時間がかかる。関数やその引数、戻り値、複雑なアルゴリズムや特別な処理についてはコメントを書く必要がある。
コメントがあるとドキュメントを作成する際にも役に立つ。

APIのドキュメントを書く

APIドキュメントのブロックにコメントを書くのは、リファレンスを用意する簡単な手段であり、コードを再検討することでコードの品質を向上させる。
時間を空けてドキュメントのコメントブロックを書いていると、問題をとらえなおし、再検討することで、改善点が明確になる。わかりやすいように書くということは読み手を意識して書くということである。
また、再考することで最初に思い浮かんだ解決方法を捨て、新たに解決方法を作り出すこともできる。こういった場合、理解が深まっている2番目以降の解決方法がより良い方法であることは言うまでもない。

ドキュメントを書くことはめんどくさく割に合わないようだが、APIのドキュメントはコードのコメントから自動生成することができる。JavaScriptには、以下の2つのツールがあり、どちらもフリーでオープンソースである。

APIドキュメントを作成する手順は以下の通り。

  • 特別にフォーマットしたコードのブロックを書く
  • コードとコメントを解析するツールを実行する
  • ツールの出力結果を公開する

タグの一部紹介は下記のページで。
【JavaScript】APIドキュメント用のタグ(メモ) - Qiita

ピアレビュー

ピアレビューとは、生成していく各段階でできる生成物のレビュー、またはその手法のこと。
ピアレビューは公式で標準のものがあり、開発プロセスにレビューを簡単に取り入れる方法である。となりにいる開発者に頼んでコードを見てもらうだけでよい。
他人にコードを読んでもらうときに、そのコードが何をしているのか理解させる必要があるため、より明快なコードを書くようになっていく。また、コードがよくなるだけでなく、レビュー者と作成者が知識を共有し、互いの経験とアプローチを学べる点も優れている。
となりに誰もいない寂しい人でも一部をオープンソースとしたり、Gitなどのソース管理システムで公開することで、ピアレビューが発生するかもしれない。

プロダクション段階でミニファイする

ミニフィケーション(minification)とは、空白やコメントなど、コード以外の部分を削除することである。これによってファイルを小さくし、ページの起動時間を短縮することができる。

ミニファイアは、空白、改行、コメントを除去するだけでなく、安全であれば変数名を短い名前に変更する。また、グローバル変数の名前を変更すると、コードが壊れるかもしれないため、ローカル変数のみを変更する。できる限りローカル変数を使ったほうがいいのは、このような理由もあるためである。

しかし、あらかじめミニファイされたコードを書いてはいけない。常にコードは、説明的な変数名、一貫した空白とインデント、コメントで書くべきである。ファイルサイズを減らすのは機械に任せましょう。

JSLintを実行する

JSLintにかけることでどのような違反を見ているのか。この投稿で挙げたもの以外にも以下のようなものがある。

  • 到達できないコード
  • 定義される前に使われている変数
  • 安全でないUTF文字
  • void、with、evalの使用
  • 正規表現の中の不適切なエスケープ文字

JSLintはJavaScriptで書かれている。多くのJavaScriptインタープリタでダウンロードできるようにウェブベースのツールが提供されている。
ダウンロードしたJSLintをテキストエディタと統合すれば、ファイルが保存されるたびにJSLintを実行するのを習慣づけることができる。

まとめ

この投稿で必須となるベストプラクティスとパターンをまとめると以下の通り。

  • グローバルの数を減らす。理想的にはひとつのアプリケーションにひとつのグローバル。
  • 関数では単独のvarを使う。これによってある箇所にあるすべての変数が一目で把握でき、変数の巻き上げが原因で起きる不具合が避けられる。
  • forループ、for-inループ、switchの書き方。「eval()はワル」。組み込みのprototypeを拡張しない
  • コーディング作法に従う。コンストラクタ、関数、変数の命名作法。