この文章は Google JavaScript Style Guide を和訳したものです. 内容の正確性は保証しません. ライセンスは原文と同じく CC-By 3.0 とします. フィードバックは Kosei Moriyama (@cou929 または cou929 at gmail.com) までお願いします. この和訳のリポジトリは こちら
Revision 2.2
JavaScript は Google の オープンソースプロジェクトにおいて使われる, 主要なクライアントサイドスクリプト言語です. このスタイルガイドでは JavaScript プログラムにおいて すべき こと, すべきでない ことをまとめています.
変数宣言には常に var をつける.
宣言時に var を付けなかった場合, その変数はグローバルコンテキストに置かれます. このことにより, 既存の変数が汚染される可能性があります. また宣言がない場合は, その変数がどのスコープなのかが分かりづらくなります. よって常に var をつけるべきです.
定数は NAMES_LIKE_THIS のように名付けます. 適切な場面で @const を使います. const キーワードは使うべきではありません.
プリミティブ値の定数の場合, 以下のようにします.
/**
* The number of seconds in a minute.
* @type {number}
*/
goog.example.SECONDS_IN_A_MINUTE = 60;
プリミティブ値でない定数の場合は @const アノテーションを使います.
/**
* The number of seconds in each of the given units.
* @type {Object.<number>}
* @const
*/
goog.example.SECONDS_TABLE = {
minute: 60,
hour: 60 * 60
day: 60 * 60 * 24
}
この記法を用いることで, 変数を定数として扱うようにコンパイラに伝えることができます.
const キーワードは Internet Explorer が認識しないため, 使うべきではありません.
常にセミコロンを使います.
コードのセミコロンを省き, セミコロンの挿入を処理系に任せた場合, 非常にデバッグが困難な問題が起こります. 決してセミコロンを省くべきではありません.
以下のコードで, セミコロンの省略が非常に危険である例を示します.
// 1.
MyClass.prototype.myMethod = function() {
return 42;
} // ここにセミコロンがない
(function() {
// この一時的なブロックスコープで初期化処理などを行う
})();
var x = {
'i': 1,
'j': 2
} // セミコロンがない
// 2. Internet Explorer や FireFox のために以下のようなコードを書く
// 普通はこんな書き方はしないけど, 例なので
[normalVersion, ffVersion][isIE]();
var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // セミコロンがない
// 3. bash 風な条件文
-1 == resultOfOperation() || die();
JavaScript は, 安全にセミコロンの存在が推測できる場合を除いて, 文の最後にセミコロンを要求します. 上記の例では関数宣言やオブジェクトや配列リテラルが文の中にあります. 閉じ括弧は文の終わりを表現するものではありません. 次のトークンが()演算子などの場合, JavaScript はそれを前の文の続きとみなしてしまいます.
これらの挙動は本当にプログラマを驚かせてしまいます. よってセミコロンを徹底すべきです.
使っても良い.
例外を独自に定義しない場合, エラー時の関数の戻り値を工夫せねばならず, エレガントではありません. エラー情報へのリファレンスを返すか, エラーメンバーを含むオブジェクトを返すことで解決できますが, これらはプリミティブな例外をハックすることで実現できます. よって適切な場面では独自の例外を使用すべきです.
常に標準の機能を使うべきです.
ポータビリティとコンパチビリティを最大化するために, 常に非標準の機能よりも標準の機能を使うべきです (例えば string[3] ではなく string.charAt(3) を使ったり, アプリケーション特有の省略記法ではなく DOM 関数を使うなど).
使用すべきでない.
プリミティブな型のラッパーオブジェクトを使う理由はありません. またそれは危険です.
var x = new Boolean(false);
if (x) {
alert('hi'); // 'hi' がアラートされる.
}
絶対にやらないでください!
しかし型キャストは問題ありません.
var x = Boolean(0);
if (x) {
alert('hi'); // これはアラートされません
}
typeof Boolean(0) == 'boolean';
typeof new Boolean(0) == 'object';
この方法はデータを number, string, boolean にキャストする際に便利です.
好ましくありません.
多層のプロトタイプヒエラルキー(Multi-level prototype hierarchies) は JavaScript が継承を実装している方法です. ユーザー定義クラスDがプロトタイプとしてユーザー定義クラスBを持っている場合, 多層のヒエラルキーになります. こうしたヒエラルキーは理解するのが難しくなります.
よって同様のことを実現したい場合は, Closure Library の goog.inherits() を使うべきです.
function D() {
goog.base(this)
}
goog.inherits(D, B);
D.prototype.method = function() {
...
};
Foo.prototype.bar = function() { ... };
メソッドやプロパティをコンストラクタに付与する方法はいくつかありますが, 次の方法を使用してください:
Foo.prototype.bar = function() {
/* ... */
};
使っても良い. ただし慎重に.
クロージャは JavaScript の中でも最も便利でよく見る機能です. クロージャについて詳しくはこちらを参照してください.
ただし一点注意すべき点は, クロージャはその閉じたスコープへのポインタを保持しているという点です. そのため, クロージャを DOM 要素に付加すると循環参照が発生する可能性があり, メモリリークの原因となります. 例えば, 以下のコードを見てください:
function foo(element, a, b) {
element.onclick = function() { /* 引数 a と b を使う */ };
}
上記の無名関数はそれらを使う・使わないにかかわらず element, a, b への参照をずっと保持しています. element はクロージャへの参照をもっているので, 循環が発生していて, gc が回収できなくなっています. この場合, コードは以下のような構造になっています:
function foo(element, a, b) {
element.onclick = bar(a, b);
}
function bar(a, b) {
return function() { /* 引数 a と b を使う */ }
}
デシリアライズ (deserialization) するときのみ使用可. (例えば RPC レスポンスを評価するときなど)
eval() はセマンティクスを混乱させやすいし, ユーザーインプットを eval() したものは危険です. 通常はもっとクリアで安全な代替手段が存在するので, 大抵の場合には eval() は使用すべきではありません. しかし eval をデシリアライズ (deserialization) に使う場合は, 代替手段よりも eval の方が便利です. (例えば RPC レスポンスを評価するときなど)
Deserialization とはバイト列をメモリ上のデータ構造に変換する処理です. 例えば, 以下のようなオブジェクトがファイルに書き出してあったとします:
users = [
{
name: 'Eric',
id: 37824,
email: 'jellyvore@myway.com'
},
{
name: 'xtof',
id: 31337,
email: 'b4d455h4x0r@google.com'
},
...
];
単にこの文字列を eval するだけで, このデータをメモリに移すことができます.
また, eval() によって RPC のレスポンスを簡単にデコードできます. 例えば XMLHttpRequest を使って RPC の呼出を行ない, サーバは JavaScript を返す場合はこのようになります:
var userOnline = false;
var user = 'nusrat';
var xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', 'http://chat.google.com/isUserOnline?user=' + user, false);
xmlhttp.send('');
// サーバは以下のようなコードを返す:
// userOnline = true;
if (xmlhttp.status == 200) {
eval(xmlhttp.responseText);
}
// userOnline は現在 true になる.
使用すべきでない.
with によってコードの意味がわかりにくくなります. with のオブジェクトはローカル変数と衝突するプロパティを持ちます. これによってプログラムの意味が大きく変わってしまいます. 例えば次のコードはどういう動作をするでしょう?
with (foo) {
var x = 3;
return x;
}
答え: anything. ローカル変数 x は foo プロパティによって上書きされます. もし x がセッターだったとき, 3 を代入することが沢山のコードを実行してしまいます. with を使ってはいけません.
オブジェクトのコンストラクタ, メソッド, クロージャのセットアップのときのみ使用可.
this の意味はトリッキーです. this はグローバルスコープを指していたり (多くの場合), 呼び出し元を指していたり (eval), DOMのノードを指していたり (イベントハンドラを HTML 要素にセットした場合), 新しく作られたオブジェクトを指していたり (コンストラクタ), なにか他のオブジェクトを指している場合 (call(), apply() された関数) もあります.
this の使用は間違えやすいので, 以下の場面以外での使用は制限されています.
オブジェクト, map, hash のキーに対してイテレーションする場合のみ使用可.
for-in ループは配列のすべての要素を走査する場合などによく誤って利用されています. これはインデックス 0 から length - 1 までをループするわけではなく, 配列プロトタイプにあるすべてのキーについてループします. 以下は for-in ループでの配列の走査を失敗する例です.
function printArray(arr) {
for (var key in arr) {
print(arr[key]);
}
}
printArray([0,1,2,3]); // 正しく動作.
var a = new Array(10);
printArray(a); // 正しく動かない.
a = document.getElementsByTagName('*');
printArray(a); // 正しく動かない.
a = [0,1,2,3];
a.buhu = 'wine';
printArray(a); // 正しく動かない.
a = new Array;
a[3] = 3;
printArray(a); // 正しく動かない.
配列には通常の for ループを使用してください.
function printArray(arr) {
var l = arr.length;
for (var i = 0; i < l; i++) {
print(arr[i]);
}
}
配列を map/hash/連想配列 として使用してはいけません.
連想配列は許可されていません, つまり配列に数値以外のインデックスを使用してはいけません. map/hash を使いたいときは配列でなくオブジェクトを使用してください. なぜならこのような機能はもともと配列ではなくオブジェクトの機能です. 配列はオブジェクトを拡張したものです (そして他の JavaScript のオブジェクト, Data, 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.';
各行の先頭の空白はコンパイラによって安全に取り除かれますが, スラッシュの後の空白によってトリッキーなエラーが発生する可能性があります. また多くの JavaScript エンジンはこの記法をサポートしていますが, これは ECMAScript 標準ではありません.
使用して良い.
Array, Object コンストラクタではなくリテラルを使ってください.
Array コンストラクタはその引数の取り方のせいでエラーを引き起こしがちです.
// 長さは 3.
var a1 = new Array(x1, x2, x3);
// 長さは 2.
var a2 = new Array(x1, x2);
// もし x1 が数字で, かつ自然数の場合, length は x1 になる.
// もし x1 が数字で, かつ自然数でない場合, 例外が発生する.
// 数字でない場合, 配列は x1 を値として持つ.
var a3 = new Array(x1);
// 長さは 0.
var a4 = new Array();
コンストラクタはこのような動作をするので, もし別のひとがコードを書き換えて, コンストラクタに2つの引数を与えていたところを1つにすると, その結果できた配列は期待する長さを持っていないかもしれません.
このようなミスを避けるために, 配列のリテラルを使用してください.
var a = [x1, x2, x3];
var a2 = [x1, x2];
var a3 = [x1];
var a4 = [];
オブジェクトの場合は, コンストラクタに配列のような紛らわしさはないのですが, 可読性と一貫性のためにリテラルを使用してください.
var o = new Object();
var o2 = new Object();
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 などはそれに比べ比較的安全ですが, デバッグ時に問題を引き起こす可能性があるので, 変更は避けてください.
基本的に次のように命名してください: functionNamesLikeThis, variableNamesLikeThis, ClassNamesLikeThis, EnumNamesLikeThis, methodNamesLikeThis, and SYMBOLIC_CONSTANTS_LIKE_THIS.
Private と Protected に関しては visibility のセクションを参考にしてください.
オプション引数には opt_ というプレフィックスをつけてください.
可変長の引数を取る場合, 最後の引数を var_args と名づけてください. ただし参照する際は var_args ではなく arguments を参照するようにしてください.
オプション引数と可変長引数に関しては @param アノテーションでもコンパイラは正しく解釈してくれます. 両方を同時に用いることが好ましいです.
getter, setter は必須ではありません. もし使う場合は getFoo(), setFoo(value) という名前にしてください. (boolean の getter の場合は isFoo() も許可されています. こちらのほうがより自然です.)
JavaScript は階層的なパッケージングや名前空間をサポートしていません.
グローバル名前衝突が起こるとデバッグは難しくなり, 2つのプロジェクトの統合も難しくなります. 名前の衝突を避け, 共有できる JavaScript コードをモジュール化するために, 以下のような規約を設けています.
グローバルスコープに出すものには, プロジェクトやライブラリ名に関連したプレフィックスを常に付けてください. 例えば “Project Sloth” の場合, sloth.* という具合です.
var sloth = {};
sloth.sleep = function() {
...
};
Closure Library や Dojo toolkit でも名前空間を定義する関数が提供されています. これらを使う場合は一貫性に注意してください.
goog.provide('sloth');
sloth.sleep = function() {
...
};
子の名前空間を作る場合は, 親の名前空間への連絡をしてください. sloth から hats というプロジェクトを始めた場合は, sloth チームに sloth.hats という名前を使用する旨を伝えてください.
“外部のコード (External code)” とはあなたのコードの外から読み込んだもので, 独立してコンパイルされたものです. 内部と外部のコードの名前空間は厳密に分けてください. もし foo.hats.* という外部ライブラリを使用した場合, 衝突の可能性があるので, 内部のコードでは foo.hats.* に何も定義してはいけません.
foo.require('foo.hats');
/**
* 間違い -- 絶対にこのようにはしないでください.
* @constructor
* @extend {foo.hats.RoundHat}
*/
foo.hats.BowlerHat = function() {
};
もし外部名前変数に新しい API を定義する必要がある場合は, 明示的に公開 API をエクスポート擦る必要があります. 一貫性とコンパイラの最適化のために, 内部のコードでは内部の API を内部の名前で呼ぶ必要があります.
foo.provide('googleyhats.BowlerHat');
foo.require('foo.hats');
/**
* @constructor
* @extend {foo.hats.RoundHat}
*/
googleyhats.BowlerHat = function() {
...
};
goog.exportSymbol('foo.hats.BowlerHat', googleyhats.BowlerHat);
ファイル名は case-sensitive なプラットフォームのために, 必ず小文字にしてください. サフィックスは .js に, 句読点は -, _ (_ よりも - を使用してください) 以外は使わないでください.
副作用なしに, 必ず動作しないといけません.
toString() メソッドを定義して, 独自のオブジェクトがどのように文字列化されるかを定義できます. ただし以下の2点が必ず守られる必要があります.
これらが守られなかった場合, 簡単に問題が引き起こされてしまいます. 例えば toString() が assert を呼び出している場合, assert はオブジェクト名をアウトプットしようとするので, toString() が必要になります.
常に必要です.
常に明示的なスコープを使用してください. ポータビリティが向上し, またクリアになります. 例えば window が content window でないアプリケーションもあるので, window に依存するようなコードは書かないでください.
基本的に C++ formatting rules に従います. 以下はそれに追加する項目です.
処理系によってセミコロンが暗黙で挿入されるのを防ぐために, かならず開き波括弧は改行せずに同じ行に書いてください.
if (something) {
// ...
} else {
// ...
}
一行に収まる場合は, 初期化を一行で行ってもかまいません.
var arr = [1, 2, 3]; // 括弧の前後に空白を入れないでください
var obj = {a: 1, b: 2, c: 3}; // 括弧の前後に空白を入れないでください
複数行に渡る初期化の場合は, ふつうのブロック同様スペース2つのインデントを行ってください.
// オブジェクトの初期化
var inset = {
top: 10,
right: 20,
bottom: 15,
left: 12
};
// 配列の初期化
this.rows_ = [
'*Slartibartfast* <fjordmaster@magrathea.com>',
'*Zaphod Beeblebrox* <theprez@universe.gov>',
'*Ford Prefect* <ford@theguide.com>',
'*Arthur Dent* <has.no.tea@gmail.com>',
'*Marvin the Paranoid Android* <marv@googlemail.com>',
'the.mice@magrathea.com'
];
// メソッドの引数としてのオブジェクト
goog.dom.createDom(goog.dom.TagName.DIV, {
id: 'foo',
className: 'some-css-class',
style: 'display:none'
}, 'Hello, world!');
identifer が長い場合, プロパティを整列させると問題を引き起こす場合があるので, 整列させないようにしてください.
CORRECT_Object.prototype = {
a: 0,
b: 1,
lengthyName: 2
};
以下のようにはしないでください.
WRONG_Object.prototype = {
a : 0,
b : 1,
lengthyName: 2
};
可能ならば, すべての関数の引数は一行にしてください. もしそれでは80文字の制限を超えてしまう場合は, 読みやすい形で複数行にしてください. スペースの節約のために各行をできるだけ80文字に近づけるように書くか, あるいは可読性のためにひとつの引数に付き一行を割り当てます. インデントは空白4つにするか, 括弧にあわせてください. 以下に典型的な例を示します.
// 空白4つのインデント, 80文字近くまで並べる. とても長い関数名で, スペースが少ない場合.
goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
// ...
};
// 空白4つのインデント, 1引数につき1行. とても長い関数名で各引数を強調したい場合
goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
veryDescriptiveArgumentNumberOne,
veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy,
artichokeDescriptorAdapterIterator) {
// ...
};
// 括弧に合わせたインデント, 80文字近くまで並べる. 引数を見やすくまとめて, スペースが少ない場合.
function foo(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
// ...
}
// 括弧に合わせたインデント, 1引数につき1行. 引数を見やすくまとめて, 各引数を強調したい場合.
function bar(veryDescriptiveArgumentNumberOne,
veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy,
artichokeDescriptorAdapterIterator) {
// ...
}
引数として無名関数を定義し渡すときは, 無名関数の中身は関数呼び出しの左端か文の左端から空白2つのインデントにします. 場合によってはコードが左に寄り過ぎてしまうので, function キーワードから空白2つではありません.
var names = items.map(function(item) {
return item.name;
});
prefix.something.reallyLongFunctionName('whatever', function(a1, a2) {
if (a1.equals(a2)) {
someOtherLongFunctionName(a1);
} else {
andNowForSomethingCompletelyDifferent(a2.parrot);
}
});
配列・オブジェクトの初期化と引数としての無名関数以外では, すべて文の左端に合わせるか, 左からスペース4つのインデントにします.
someWonderfulHtml = '' +
getEvenMoreHtml(someReallyInterestingValues, moreValues,
evenMoreParams, 'a duck', true, 72,
slightlyMoreMonkeys(0xfff)) +
'';
thisIsAVeryLongVariableName =
hereIsAnEvenLongerOtherFunctionNameThatWillNotFitOnPrevLine();
thisIsAVeryLongVariableName = 'expressionPartOne' + someMethodThatIsLong() +
thisIsAnEvenLongerOtherFunctionNameThatCannotBeIndentedMore();
someValue = this.foo(
shortArg,
'非常に長い文字列型の引数 - 実際にはこのようなケースはとてもよくあります.',
shorty2,
this.bar());
if (searchableCollection(allYourStuff).contains(theStuffYouWant) &&
!ambientNotification.isActive() && (client.isAmbientSupported() ||
client.alwaysTryAmbientAnyways()) {
ambientNotification.activate();
}
論理的に関連のある行をまとめるために空白行を使用してください.
doSomethingTo(x);
doSomethingElseTo(x);
andThen(x);
nowDoSomethingWith(y);
andNowWith(z);
演算子は常に先行する行においてください. そうしないと暗黙のセミコロンの問題が発生します. 改行を入れる場合は上記のルールにのっとってインデントします.
var x = a ? b : c; // 可能ならば1行に
// 空白4つのインデント
var y = a ?
longButSimpleOperandB : longButSimpleOperandC;
// 最初のオペランドに合わせたインデント
var z = a ?
moreComplicatedB :
moreComplicatedC;
必要なところだけで使います.
構文上・ 意味上不可欠な場面以外では, 丸括弧を使わないようにします.
単項演算子 (delete, typeof) や void に丸括弧を使用してはいけません. また return や throw, case, new などのあとにも付けません.
" よりも ' を使ってください.
ダブルクオートよりもシングルクオートを使ってください. そのほうが HTML を含む文字列を作る際に便利です.
var msg = 'なんらかの HTML';
JSDoc の @private, @protected アノテーションが推奨されます.
クラス, 関数, プロパティの visibility レベルの指定に, JSDoc の @private, @protected アノテーションを使うことが推奨されます.
@private なグローバル変数と関数は同じファイルのコードからのみアクセスできます.
@private なコンストラクタは, 同じファイルの同じインスタンスのメンバーからアクセスできます. また @private コンストラクタは同じファイルのパブリックな静的プロパティと instanceof 演算子からアクセスできます.
グローバル変数・関数・コンストラクタは @protected にはなりません.
// File 1.
// AA_PrivateClass_ と AA_init_ はグローバルで同じファイルからなのでアクセスできる
/**
* @private
* @constructor
*/
AA_PrivateClass_ = function() {
};
/** @private */
function AA_init_() {
return new AA_PrivateClass_();
}
AA_init_();
@private なプロパティは同じファイルのすべてのコードからアクセスできます. またそのプロパティがクラスに属していた場合, そのメソッドが含まれるクラスの静的メソッドとインスタンスメソッドからもアクセスできます.
@protected なプロパティは同じファイルのすべてのコードからアクセスできます. またそのプロパティを含むクラスの静的メソッドと, そのクラスのサブクラスからもアクセスできます.
// File 1.
/** @constructor */
AA_PublicClass = function() {
};
/** @private */
AA_PublicClass.staticPrivateProp_ = 1;
/** @private */
AA_PublicClass.prototype.privateProp_ = 2;
/** @protected */
AA_PublicClass.staticProtectedProp = 31;
/** @protected */
AA_PublicClass.prototype.protectedProp = 4;
// File 2.
/**
* @return {number} The number of ducks we've arranged in a row (一列にならべるアヒルの数).
*/
AA_PublicClass.prototype.method = function() {
// これら2つのプロパティへの合法的なアクセス
return this.privateProp_ + AA_PublicClass.staticPrivateProp_;
};
// File 3.
/**
* @constructor
* @extends {AA_PublicClass}
*/
AA_SubClass = function() {
// protected な静的プロパティへの合法的なアクセス
AA_PublicClass.staticProtectedProp = this.method();
};
goog.inherits(AA_SubClass, AA_PublicClass);
/**
* @return {number} The number of ducks we've arranged in a row (一列にならべるアヒルの数).
*/
AA_SubClass.prototype.method = function() {
// protected なインスタンスプロパティへの合法的なアクセス
return this.protectedProp;
};
コンパイラによって強制されます.
JSDoc で型についてドキュメント化するときはできるだけ型を特定し正確にしてください. サポートしているのは JS2 と JS1.x の型です.
JS2 のプロポーサルには JavaScript の型を指定するための言語が記述されています. この言語を使って JSDoc のドキュメントに関数パラメータや返り値の型を記述します.
JS2 のプロポーサルの発展によって, 記法にも変化がありました. コンパイラは古い記法をサポートしていますがそれらは非推奨です.
Note
訳注
省略しました. 詳しくは原文にある表を参照してください. 後日補完します.
Note
訳注
省略しました. 詳しくは原文にある表を参照してください. 後日補完します.
JavaScript は弱い型付けの言語なので, 関数の引数やクラスのプロパティの オプション引数, nullable (ヌルを取り得る), undefine の3つの違いについて知る必要があります.
オブジェクトの型 (あるいは参照型) はデフォルトで nullable です. しかし関数の型はデフォルトで nullable ではありません. オブジェクトは文字列, 数字, 真偽値, undefine 以外のものか null として定義されます. 例として以下のコードを示します.
/**
* コンストラクタの引数 value で初期化されるクラス.
* @param {Object} value Some value.
* @constructor
*/
function MyClass(value) {
/**
* 何らかの値.
* @type {Object}
* @private
*/
this.myValue_ = value;
}
このコードではコンパイラに myValue_ プロパティはオブジェクトか null をとるように指定しています. もし myValue_ が null を取りえなくする場合は次のようにします。
/**
* コンストラクタの引数 value (なんらかの null でない値) で初期化されるクラス.
* @param {!Object} value Some value.
* @constructor
*/
function MyClass(value) {
/**
* 何らかの値.
* @type {!Object}
* @private
*/
this.myValue_ = value;
}
この場合, もし myClass が null で初期化されたとき, コンパイラがワーニングを出します.
関数のオプションパラメータは実行時に undefined に成り得ます. よってそれらがクラスのプロパティとして使われる場合は, 以下のように定義する必要があります.
/**
* コンストラクタの引数 value (オプション) で初期化されるクラス.
* @param {Object=} opt_value Some value (optional).
* @constructor
*/
function MyClass(opt_value) {
/**
* 何らかの値.
* @type {Object|undefined}
* @private
*/
this.myValue_ = opt_value;
}
この場合 myValue_ はオブジェクト, null, undefined を取り得ます.
ここで opt_value は {Object|undefined} ではなく {Object=} と定義されていることに注意してください. これはオプションのパラメータは定義上そもそも undefined に成りえるためです. 可読性のためわざわざ undefined を取りうることを明示する必要はありません.
最後に, nullable と オプション引数 の指定は直行しています. よって以下の4つの宣言はすべて別の意味です.
/**
* 4つのうち2つは nullable, 2つはオプション
* @param {!Object} nonNull Mandatory (must not be undefined), must not be null.
* @param {Object} mayBeNull Mandatory (must not be undefined), may be null.
* @param {!Object=} opt_nonNull Optional (may be undefined), but if present,
* must not be null!
* @param {Object=} opt_mayBeNull Optional (may be undefined), may be null.
*/
function strangeButTrue(nonNull, mayBeNull, opt_nonNull, opt_mayBeNull) {
// ...
};
JSDoc を使用してください.
ファイル, クラス, メソッドをドキュメンテーションするために JSDoc のコメントを使用してください. インラインのコメントには // を使います. 加えて, C++ style for comments に基本的に従います. つまり以下のような内容を記述します.
文章が断片的になることは避けて, 文の開始は大文字, 文の最後には句点を入れます.
新人プログラマがあなたのコードをメンテナンスすることを想定して書いてください. そうすればきっとうまくいきます!
コンパイラは JSDoc で書かれたコメントから情報を抜き出し, validation や不要なコードの削除, コードの圧縮などに使用します. よって JSDoc の正しい記法で記述してください.
トップレベルコメントはそのコードに詳しくない読者を対象として, そのファイルが何をしているのかを説明するコメントです. ファイルの内容, 作者, コンパチビリティの情報などを記述します.
// Copyright 2009 Google Inc. All Rights Reserved.
/**
* @fileoverview ファイルの説明, 使用方法や
* 依存関係の情報など.
* @author user@google.com (Firstname Lastname)
*/
クラスコメントには説明と使用方法を記述します. コンストラクタのパラメータについても記述します. もし他のクラスを継承している場合は @extends タグを使用します. インタフェースを実装している場合は @implements タグを使用します.
/**
* なんだか楽しいクラス.
* @param {string} arg1 An argument that makes this more interesting.
* @param {Array.<number>} arg2 List of numbers to be processed.
* @constructor
* @extends {goog.Disposable}
*/
project.MyClass = function(arg1, arg2) {
// ...
};
goog.inherits(project.MyClass, goog.Disposable);
説明とパラメータについて記述します. フルセンテンスを書きます. 文は第三者が宣言している文体で書きます.
/**
* テキストをなにか全く別のテキストに変換する
* @param {string} arg1 An argument that makes this more interesting.
* @return {string} Some return value.
*/
project.MyClass.prototype.someMethod = function(arg1) {
// ...
};
/**
* MyClass のインスタンスを処理して何かを返す関数
* @param {project.MyClass} obj Instance of MyClass which leads to a long
* comment that needs to be wrapped to two lines.
* @return {boolean} Whether something occured.
*/
function PR_someMethod(obj) {
// ...
}
パラメータのないシンプルな getter メソッドの場合は説明を省略できます.
/**
* @return {Element} あるコンポーネントの要素.
*/
goog.ui.Component.prototype.getElement = function() {
return this.element_;
};
プロパティにもコメントを付けると良いです.
/**
* 1 pane ごとの最大数.
* @type {number}
*/
project.MyClass.prototype.someProperty = 4;
型チェックがある文の型を正確に推論できない場合, 型キャストのコメントを付加して括弧でくくります. 括弧は必ず必要です. コメントと共に括弧でくくります.
/** @type {number} */ (x)
(/** @type {number} */ x)
@param, @return, @supported, @this, @deprecated アノテーションが複数行に渡る場合, 空白4つのインデントを使います.
/**
* 説明文が長く, 複数行にまたがった場合の例.
* @param {string} これはとても説明文の長い引数の例です. 複数行にまたがる場合は空白4つ分の
* インデントを入れてください.
* @return {number} これはとても説明文の長い返り値の例です. 複数行にまたがる場合は空白4つ分の
* インデントを入れてください.
*/
project.MyClass.prototype.method = function(foo) {
return 5;
};
@fileoverview のコメントはインデントしてはいけません.
文章の左端でそろえる方法も可能ですが, 推奨されません. 変数名が変わったときに毎回対応する必要が出てくるためです.
/**
* これらは推奨されないインデントの例です.
* @param {string} これはとても説明文の長い引数の例です. 複数行にまたがっていますが, 上の例のように
* 4スペースのインデントではありません.
* @return {number} これはとても説明文の長い返り値の例です. 複数行にまたがっていますが, 4つの空白ではなく
* 説明文の開始位置にあわせてインデントしています.
*/
project.MyClass.prototype.method = function(foo) {
return 5;
};
/**
* 3つの状態を持つ Enum
* @enum {number}
*/
project.TriState = {
TRUE: 1,
FALSE: -1,
MAYBE: 0
};
Enum は有効な型でもあるので, パラメータの型などで使用することができます.
/**
* プロジェクトの状態をセットする関数
* @param {project.TriState} state New project state.
*/
project.setState = function(state) {
// ...
};
型が複雑になることもあります. 例えばある要素を引数としてとる関数はこのようになります:
/**
* @Param {string} tagName
* @param {(string|Element|Text|Array.<Element>|Array.<Text>)} contents
* @return {Element}
*/
goog.createElement = function(tagName, contents) {
...
};
@typedef タグで型を定義することができます.
/** @typedef {(string|Element|Text|Array.<Element>|Array.<Text>)} */
goog.ElementContent;
/**
* @param {string} tagName
* @param {goog.ElementContent} contents
* @return {Element}
*/
goog.createElement = function(tagName, contents) {
...
};
Note
訳注
省略しました. 詳しくは原文にある表を参照してください. 後日補完します.
http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml?showone=Comments#Comments
JavaDoc のように JSDoc でも多くの HTML タグがサポートされています.
よってプレインテキスト上のフォーマットは考慮されなくなります. JSDoc では空白に頼ったフォーマットをしないでください.
/**
* 3つの要素から重みを計算する:
* items sent
* items received
* last timestamp
*/
このコードは次のように表示されます
3つの要素から重みを計算する: items sent items received items received
代わりに以下のように記述してください.
/**
* 3つの要素から重みを計算する:
* <ul>
* <li>items sent
* <li>items received
* <li>last timestamp
* </ul>
*/
また, HTML として解釈できないようなタグを書かないでください.
/**
* <b> タグを <span> タグへ変換する.
*/
これは次のように表示されます.
タグを タグへ変換する.
さらに, プレインテキストのドキュメントも同様によく読まれます. あまり HTML 表記にこだわりすぎないでください.
/**
* <b> タグを <span> タグへ変換する.
*/
‘少なり’, ‘大なり’ 記号をわざわざ書かなくても読者には伝わるでしょう. 以下のように記述してください.
/**
* 'b' タグを 'span' タグへ変換する.
*/
Closure Compiler のような JavaScript コンパイラを使うことが推奨されています.
以下はすべて boolean 表現では false になります.
以下は true になるので注意してください
以上より, 以下のようなコードの代わりに:
while (x != null) {
以下のように短く書くことができます (ただし x は 0 や空文字列や false にならないと仮定しています).
while (x) {
もし文字列が null でも空でもないことをチェックしたいときは:
if (y != null && y != '') {
こうではなく, 以下のようによりスマートに記述できます.
if (y) {
ただし, boolean 表現には直感的でないものが多くあるので注意してください.
Boolean('0') == true
'0' != true
0 != null
0 == []
0 == false
Boolean(null) == false
null != true
null != false
Boolean(undefined) == false
undefined != true
undefined != false
Boolean([]) == true
[] != true
[] == false
Boolean({}) == true
{} != true
{} != false
このコードの代わりに:
if (val != 0) {
return foo();
} else {
return bar();
}
以下のように書けます.
return val ? foo() : bar();
3項演算子は HTML を生成するときにも便利です.
var html = '<input type="checkbox"' +
(isChecked ? ' checked' : '') +
(isEnabled ? '' : ' disabled') +
' name="foo">';
2項の boolean 演算子はショートサーキットで, 最後の項まで評価されます.
|| は “デフォルト演算子” とも呼ばれます. 以下のコードは,
/** @param {*=} opt_win */
function foo(opt_win) {
var win;
if (opt_win) {
win = opt_win;
} else {
win = window;
}
// ...
}
次のように書き換えられます.
/** @param {*=} opt_win */
function foo(opt_win) {
var win = opt_win || window;
// ...
}
同様に && 演算子を使うことでもコードを短縮できます. このようなコードの代わりに:
if (node) {
if (node.kids) {
if (node.kids[index]) {
foo(node.kids[index]);
}
}
}
次のように書けます.
if (node && node.kids && node.kids[index]) {
foo(node.kids[index]);
}
あるいは, 次のような書き方も可能です.
var kid = node && node.kids && node.kids[index];
if (kid) {
foo(kid);
}
しかしながら, この例はすこしやりすぎでしょう.
node && node.kids && node.kids[index] && foo(node.kids[index]);
このようなコードはよく見かけられます:
function listHtml(items) {
var html = '<div class="foo">';
for (var i = 0; i < items.length; ++i) {
if (i > 0) {
html += ', ';
}
html += itemHtml(items[i]);
}
html += '</div>';
return html;
}
しかしこの書き方は Internet Explorer では遅くなります. 次の書き方がベターです:
function listHtml(items) {
var html = [];
for (var i = 0; i < items.length; ++i) {
html[i] = itemHtml(items[i]);
}
return '<div class="foo">' + html.join(', ') + '</div>';
}
配列を stringbuilder として使い, myArray.join('') で文字列に変換することも可能です. また push() で配列の要素を追加するよりもインデックスを指定して追加する方が高速なので, そちらを用いるべきです.
ノードリストの多くは, ノードのイテレータとフィルタから実装されています. よって, 例えばリストの長さを取得したい場合は O(n), またリストの要素を操作しそれぞれについて長さをチェックした場合は O(n^2) かかってしまいます.
var paragraphs = document.getElementsByTagName('p');
for (var i = 0; i < paragraphs.length; i++) {
doSomething(paragraphs[i]);
}
代わりにこう書いたほうがベターです:
var paragraphs = document.getElementsByTagName('p');
for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) {
doSomething(paragraph);
}
これはすべてのコレクション, 配列に対してうまく動きます. 要素がなくなるまでループし, 最後には false となりループが終了します.
childNodes をたどる場合は, firstChild や nextSibling プロパティを使うことができます.
var parentNode = document.getElementById('foo');
for (var child = parentNode.firstChild; child; child = child.nextSibling) {
doSomething(child);
}
一貫性をもたせてください
あなたがコードを書くとき, どのようなスタイルで書くかを決める前に, 少しまわりのコードを見るようにしてください. もし周りのコードが算術演算子の両端にスペースを入れていれば, あなたもそうすべきです. もしまわりのコードのコメントが, ハッシュマーク # を使って矩形を描いていたとしたら, あなたもまたそうすべきです.
コーディングスタイルのガイドラインを策定することのポイントは, コーディングの共通の語彙をもって, どう書くか ではなく 何を書くか に集中できるようにすることです. 私たちはここでグローバルなスタイルのルールを提供したので, 人々は共通の語彙を得られたことになります しかしローカルなスタイルもまた重要です. もしあなたが追加したコードがあまりにも周りのコードと違っていた場合, コードを読む人のリズムが乱されてしまいます. それは避けてください.