Google JavaScript スタイルガイド - 日本語訳
JavaScriptの言語仕様に関するルール
最終更新:
aias-jsstyleguide2
-
view
include_js plugin Error : このプラグインで利用できない命令または文字列が入っています。
JavaScriptの言語仕様に関するルール
- 各項目の左側にある三角ボタン
で、詳細情報の表示・非表示を切り替えられます。また下のボタンを使うと全項目を一度に変更することができます。
全て切り替える
var
常に、varと共に変数を宣言します。
varの指定がない場合、変数はグローバルコンテキストに置かれ、既存の値を上書きする可能性があります。またもし宣言そのものがなければ、その変数のスコープがどこにあるのか分かりにくくなります(その変数はローカルスコープと同じように、DocumentやWindowの中に存在していてもよいのです)。従って常にvarと共に変数を宣言してください。
定数
- 定数値にはNAMES_LIKE_THISのような名前をつけます。
- 変数やプロパティの参照先が固定されている(上書きできない)ことを示すには、@constを使います。
- Internet Explorerがサポートしないconst キーワードは決して使いません。
定数値
ある値を一定で変化しないものとして扱いたい場合、名前をCONSTANT_VALUE_CASE.ALL_CAPSのように全て大文字にし、さらに@constによって上書きできないことを示してください。
プリミティブ型(number、string、boolean)はもともと定数値です。
オブジェクトの不変性の判断は、より主観的です - 観察可能な状態変化が起きていない場合のみ、オブジェクトは不変とみなされるべきです。ただしCompilerはこの考え方を強制しません。
(変数・プロパティへの)定数ポインタ
変数やプロパティに付けられた@constアノテーションは、それらを上書きできないことを表します。この規則はCompilerによるビルド時に強制されます。またこの動作はconst キーワードとも矛盾しません。(Internet Explorerがサポートしないため、constキーワードは使いませんが。)
メソッドに付けられた@constアノテーションは、サブクラスがそのメソッドをオーバーライドできないことを表します。
コンストラクタに付けられた@constアノテーションは、そのクラスをサブクラス化できないことを表します。(Javaにおける@finalと同じです)
例
@constは、(CONSTANT_VALUES_CASE形式の名前のように)変数の値そのものが一定であることを必ずしも意味しません。一方CONSTANT_VALUES_CASE形式の名前は@constの示す内容を内包しています。
goog.example.TIMEOUT_IN_MILLISECONDS = 60;
大文字だけの名前は@constの内容を含むため、この秒数は定数です。値の上書きはできません。
オープンソースのコンパイラは@constがついていないシンボルの上書きを許容すると思われます。
/**
* 文字列を返すURLのマップ
* @const
*/MyClass.fetchedUrlCache_ = new goog.structs.Map();
/**
* サブクラス化できないクラス
* @const
* @constructor
*/sloth.MyFinalClass = function() {};
このケースではプロパティ値であるオブジェクトは上書きされませんが、オブジェクトの値は頻繁に変化し一定ではありません。(このため名前には大文字ではなくキャメルケースが使われています)
セミコロン
常にセミコロンを使います。
暗黙的なセミコロンの挿入に依存することは、微妙な、デバッグしにくい問題を引き起こします。もっと良い方法を選びましょう。
次のコードのセミコロンの無い箇所は、その危険性を示す典型的な例です:
// 1.
MyClass.prototype.myMethod = function() {
return 42;
} // ここにはセミコロンがありません。
MyClass.prototype.myMethod = function() {
return 42;
} // ここにはセミコロンがありません。
(function() {
// ローカルスコープを生成する関数の中に書かれた初期化処理。
})();
// ローカルスコープを生成する関数の中に書かれた初期化処理。
})();
var x = {
'i': 1,
'j': 2
} // ここにはセミコロンがありません。
'i': 1,
'j': 2
} // ここにはセミコロンがありません。
// 2. Internet ExplorerかFirefoxかによって処理を振り分けます。
// こんなコードは書かないとは思いますが、とりあえずお許しを。
[ffVersion, ieVersion][isIE]();
// こんなコードは書かないとは思いますが、とりあえずお許しを。
[ffVersion, ieVersion][isIE]();
var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // ここにはセミコロンがありません。
// 3. bash風の条件分岐。
-1 == resultOfOperation() || die();
-1 == resultOfOperation() || die();
何が起きるか?
- JavaScriptエラー - 先ず、42を返す関数が2番目の関数を引数として実行されます。次に42が "コール" され、数字なのでエラーが発生します。
- x[ffVersion, ieVersion][isIE]()を実行しようとしたランタイムから、おそらく 'undefined にはそのようなプロパティはありません' という風なエラーが返されます。
- 配列マイナス1はNaNとなり、NaNはどんな値とも一致しないため必ずdie()が呼び出されます(たとえresultOfOperation()の戻り値がNaNだったとしても)。THINGS_TO_EATにはdie()の戻り値が代入されます。
なぜ?
その存在が安全に推測される場合を除き、JavaScriptは文(statement)の末尾にセミコロンを要求します。上の各例では、関数宣言、オブジェクト、配列リテラルは1つの文の内側にいると見なされています。閉じ括弧は文の終了を表すには不十分です。JavaScriptは中置演算子(+、-など)や括弧では決して文を終了させません。
このことは本当に人々を驚かせます。従って文の末尾は確実にセミコロンで終わらせるべきです。
セミコロンと関数について
関数式の後ろにはセミコロンを付けますが、関数宣言の後ろには付けません。その違いを示す非常に分かりやすい例がこれです:
var foo = function() {
return true;
}; // ここにはセミコロンを付けます
function foo() {
return true;
} // ここにはセミコロンを付けません
ネストされた関数
使えます。
ネストされた関数は、例えば継続(continuation)の生成やヘルパー関数の隠蔽を行う際に、とても役立ちます。遠慮なく使いましょう。
ブロック内での関数宣言
使わないでください。
次のように書いてはいけません:
if (x) {
function foo() {}
}
function foo() {}
}
ほとんどのスクリプトエンジンはブロック内での関数宣言をサポートしていますが、それはECMAScript(ECMA-262,13及び14項を参照)の仕様には含まれていません。各エンジンの実装は、互いの間でも将来のECMAScriptに対しても、一貫性が保証されていません。ECMAScriptは関数の宣言をスクリプトのトップレベルか関数内のスコープでのみ許しています。代わりに変数へ関数を代入すれば、ブロック内でも関数を定義することが可能です:
if (x) {
var foo = function() {};
}
例外
使えます。
もし自明でない何かを行うのであれば(アプリケーション開発フレームワークの利用など)、基本的に例外は避けられません。使いましょう。
独自の例外
使えます。
独自の例外を使わない場合、何かの値を返す関数が更にエラー情報も返そうとすると、その方法はトリッキーかつエレガントでないものになりそうです。戻り値をエラー情報への参照にするか、あるいはエラー情報をメンバに持つオブジェクトに変えることになるでしょう。これらは本質的に、原始的な例外ハンドリングのハックと言えます。必要であればどんどん独自の例外を使ってください。
標準機能
標準機能は非標準の機能よりも常に優先されます。
移植性と互換性を最大化するため、常に標準化された機能の方を選択してください。(例えばstring[3]ではなく string.charAt(3)を、DOM要素へのアクセスには特定のアプリケーションが提供する簡略化された機能ではなく、DOMの標準機能を使います)
プリミティブ型のラッパーオブジェクト
使わないでください。
プリミティブ型のラッパーオブジェクトは使う理由がないだけでなく、危険ですらあります:
var x = new Boolean(false);
if (x) {
alert('hi'); // 'hi'と表示されます。
}
if (x) {
alert('hi'); // 'hi'と表示されます。
}
このようにしてはいけません!
ただし、型のキャストに使うのは全く問題ありません:
var x = Boolean(0);
if (x) {
alert('hi'); // これは決してアラートされません。
}
typeof Boolean(0) == 'boolean';
typeof new Boolean(0) == 'object';
これは何らかのデータを数値、文字列、論理値に変換するにはとても良い方法です。
多段階のプロトタイプ階層
好ましくありません。
多段階のプロトタイプ階層はJavaScriptが実装する継承手法です。ユーザ定義クラス D のプロトタイプを別のユーザ定義クラス B にすると多段階の階層構造が作れます。しかしこれらの階層構造を正しく把握しておくのは、最初の状態と比べるとかなり難しいことです。
このため、継承にはClosure Libraryのgoog.inherits()のようなライブラリ関数を使うのが最善です。
function D() {
goog.base(this)
}
goog.inherits(D, B);
D.prototype.method = function() {
...
};
メソッドとプロパティの定義
/** @constructor */ function SomeConstructor() { this.someProperty = 1; } Foo.prototype.someMethod = function() { ... };
"new"によって生成されるオブジェクトにメソッドやプロパティを付け加えるにはいくつかの方法がありますが、メソッドではこのスタイルが推奨されます:
Foo.prototype.bar = function() {
/* ... */
};
その他のプロパティでは、コンストラクタの中で初期化を行うスタイルが推奨されます:
/** @constructor */
function Foo() {
this.bar = value;
}
なぜ?
現在のJavaScriptエンジンは、オブジェクトをその「形状」に基いて最適化します。プロパティの追加(プロトタイプに設定された値のオーバーライドも含まれます)はオブジェクトの形状を変化させ、パフォーマンスの低下をもたらします。
delete
より好ましいのは this.foo = null の方です。
Foo.prototype.dispose = function() {
delete this.property_;
};
delete this.property_;
};
ではなく、こう書きましょう:
Foo.prototype.dispose = function() {
this.property_ = null;
};
現代のJavaScriptエンジンにおいて、プロパティの数を増減させる処理は、値の再割り当てに比べはるかに低速です。次の場合を除き、deleteキーワードは避けるべきです。
- プロパティ名をキーとする反復処理を行なっており、そのキーを削除する必要があるとき
- if (key in obj)の結果を変更する必要があるとき
クロージャ
使えます、しかし慎重に。
クロージャを作成できる能力は、おそらく最も役に立つ、しかししばしば見落とされているJSの特長です。クロージャの動作についての良い解説はこちらをご覧ください。
しかし1つ覚えておいて欲しいのは、クロージャは自身を取り囲むスコープへのポインタを保持しているという点です。このためクロージャをDOM要素ヘ結びつけると、循環参照が発生しメモリリークとなることがあります。例えば次のコードです:
function foo(element, a, b) {
element.onclick = function() { /* a と b を使う
*/ };
}
クロージャ関数は element、a、bへの参照を持っています。elementを使わないとしてもです。elementもまたクロージャへの参照を保持しており、そこでガーベジコレクションが回収できない循環ができてしまっています。この状況では、コードは次のように構成されるべきです:
function foo(element, a, b) {
element.onclick = bar(a, b);
}
function bar(a, b) {return function() { /* a と b を使う */ };}
eval()
コードローダまたはREPL(Read-eval-print loop)にのみ使います。
eval()はセマンティクスに混乱を引き起こします。またユーザの入力を含む文字列をeval()することは危険な行為です。たいていはもっと適切、明確、安全なコードの書き方が存在するため、通常その利用は許容されません。
JSONを用いたRPCの際、データの解釈にはeval()の代わりにJSON.parse()を使います。
以下のような結果を返すサーバがあるとします:
{
"name": "Alice",
"id": 31502,
"email": "looking_glass@example.com"
}
var userInfo = eval(feed);
var email = userInfo['email'];
var email = userInfo['email'];
配信データが悪意あるJavaScriptコードを含むものに改変されていたとして、eval()を使っていたらコードはそのまま実行されてしまうでしょう。
var userInfo = JSON.parse(feed);
var email = userInfo['email'];
JSON.parse()を使うと、(全ての実行可能なJavaScriptを含む)不正なJSONに対して例外が発生します。
with() {}
使わないでください。
withの使用はプログラムのセマンティクスを曇らせます。withで指定されたオブジェクトはローカル変数と衝突するようなプロパティを持てるため、プログラムの意図が大きく変えられてしまうおそれがあります。例えばこのコードでは何が起きるでしょうか?
with (foo) {
var x = 3;
return x;
}
var x = 3;
return x;
}
答えは「あらゆることが起きる」です。ローカル変数xはfooのプロパティに乗っ取られます。もしxがsetterだった場合、3の代入は他の多くのコードを実行させる原因となりえます。withを使ってはいけません。
this
オブジェクトのコンストラクタとメソッド、クロージャの作成時にのみ使います。
thisのセマンティクスは扱いが難しく、場合によってその参照先はグローバルオブジェクト(ほとんどはこれです)、呼び出し元のスコープ(eval内)、DOMツリー内のノード(要素に追加されたHTMLイベントハンドラ内)、新しく生成されたオブジェクト(コンストラクタ内)、何か他のオブジェクト(call()またはapply()された関数内)と変化します。
thisは容易に間違いの原因になってしまうので、本当に必要な次の場面だけに使用を限定します。
- コンストラクタの中
- オブジェクトのメソッドの中(クロージャの作成時を含む)
for-in ループ
オブジェクト / マップ / ハッシュ内をキーによって走査する場合のみ使えます。
for-inループはしばしば 配列(Array)内の要素をループ処理するという、間違った使い方をされています。for-inループは 0からlength-1までをループするのではなく、オブジェクトとそのプロトタイプチェーンに存在するキーを走査するものです。従ってそのような使い方はエラーを起こしがちです。失敗例をいくつか示します:
function printArray(arr) {
for (var key in arr) {
print(arr[key]);
}
}
for (var key in arr) {
print(arr[key]);
}
}
printArray([0,1,2,3]); // これは動作します。
var a = new Array(10);
printArray(a); // これは正しくありません。
printArray(a); // これは正しくありません。
a = document.getElementsByTagName('*');
printArray(a); // これは正しくありません。
printArray(a); // これは正しくありません。
a = [0,1,2,3];
a.buhu = 'wine';
printArray(a); // これも正しくありません。
a.buhu = 'wine';
printArray(a); // これも正しくありません。
a = new Array;
a[3] = 3;
printArray(a); // これも正しくありません。
a[3] = 3;
printArray(a); // これも正しくありません。
配列のループには必ず通常のfor文を使ってください。
function printArray(arr) {
var l = arr.length;
for (var i = 0; i < l; i++) {
print(arr[i]);
}
}
連想配列
決してArrayをマップ / ハッシュ / 連想配列として使わないでください。
Arrayを連想配列として使うことは許されていません..もっと正確に言えば、数字インデックス以外で配列要素にアクセスすることは許されていません。もしマップやハッシュが必要なのであれば、Arrayの代わりにObjectを使いましょう。実際、連想配列に求められている機能はArrayではなくObjectに実装されており、ArrayはただObjectを拡張しているに過ぎません(その他のJSの全てのオブジェクト、つまり Date、RegExp、Stringなども同様です)。
複数行の文字列リテラル
使わないでください。
このような書き方は禁止です:
var myString = 'A rather long string of English text, an error message \
actually that just keeps going and going -- an error \
message to make the Energizer bunny blush (right through \
those Schwarzenegger shades)! Where was I? Oh yes, \
you\'ve got an error and all the extraneous whitespace is \
just gravy. Have a nice day.';
actually that just keeps going and going -- an error \
message to make the Energizer bunny blush (right through \
those Schwarzenegger shades)! Where was I? Oh yes, \
you\'ve got an error and all the extraneous whitespace is \
just gravy. Have a nice day.';
各行の先頭にある空白はコンパイル時にも安全に取り除かれることはなく、バックスラッシュ(\)の後ろの空白は分かりにくいエラーをもたらします。またこの記法はほとんどのエンジンでサポートされているとはいえ、ECMAScriptの標準ではありません。
代わりに、文字列の結合を使ってください:
var myString = 'A rather long string of English text, an error message ' +
'actually that just keeps going and going -- an error ' +
'message to make the Energizer bunny blush (right through ' +
'those Schwarzenegger shades)! Where was I? Oh yes, ' +
'you\'ve got an error and all the extraneous whitespace is ' +
'just gravy. Have a nice day.';
配列リテラルとオブジェクトリテラル
使えます。
ArrayとObjectのコンストラクタではなく、配列リテラルとオブジェクトリテラルを使って下さい。
Arrayコンストラクタの引数はエラーの原因になりがちです:
// 配列の長さは3。
var a1 = new Array(x1, x2, x3);
var a1 = new Array(x1, x2, x3);
// 配列の長さは2。
var a2 = new Array(x1, x2);
var a2 = new Array(x1, x2);
// もし x1 が自然数であれば、配列の長さは x1 になります。
// もし x1 が数値でかつ自然数でなければ、例外が発生します。
// それ以外なら、配列は x1 を値とする長さ1の配列になります。
var a3 = new Array(x1);
// もし x1 が数値でかつ自然数でなければ、例外が発生します。
// それ以外なら、配列は x1 を値とする長さ1の配列になります。
var a3 = new Array(x1);
// 配列の長さは0。
var a4 = new Array();
var a4 = new Array();
このため、誰かが引数の数を2つから1つに変更すると、その配列の長さは想定と合わなくなる可能性があります。
これらの奇妙な事例は、より可読性の高い配列リテラルを使用することで避けられます:
var a = [x1, x2, x3];
var a2 = [x1, x2];
var a3 = [x1];
var a4 = [];
Objectコストラクタには同じ問題はありませんが、可読性と一貫性の面からオブジェクトリテラルを使うべきです:
var o = new Object();
var o2 = new Object();
o2.a = 0;
o2.b = 1;
o2.c = 2;
o2['strange key'] = 3;
o2.a = 0;
o2.b = 1;
o2.c = 2;
o2['strange key'] = 3;
上は、こう書かれるべきです:
var o = {};
var o2 = {
a: 0,
b: 1,
c: 2,
'strange key': 3
};
組込みオブジェクトのプロトタイプの書き換え
しないでください。
Object.prototypeやArray.prototypeのような組込みオブジェクトのプロトタイプを書き換えることは、固く禁じます。同じ組込みであってもFunction.prototypeなどはそれほど危険ではありませんが、それでもデバッグの難しい問題を引き起こす可能性があります。よって書き換えは避けるべきです。
Internet Explorerの条件付きコメント
使わないでください。
これは禁止です:
var f = function () {
/*@cc_on if (@_jscript) { return 2* @*/ 3; /*@ }
@*/
};
条件付きコメントはライタイムのJavaScriptシンタックス・ツリーを改変してしまうので、ツールによる自動化の妨げになります。
* ミリ秒単位のリクエストタイムアウト時間
* @type {number}
*/