『モテる JavaScript』フォローアップ – その 2 「JavaScript の 慣例的な記述方法とマナー」
4 月 27 日(2013) に開催されました、第4回「ブラウザー勉強会」に『モテる JavaScript』というセッションのフォローアップ第二回目です。
前回より以下のアジェンダでセッションの内容を紹介しています。
アジェンダ
- はじめに
- 知っておきたい JavaScript の 慣例的な記述方法とマナー
- 使われているとなんかカッコ良い JavaScript の機能
なお、当日使用したスライドは以下になります。
今回は 『知っておきたい JavaScript の 慣例的な記述方法とマナー』 についてです。
知っておきたい JavaScript の 慣例的な記述方法とマナー
JavaScript には、言語の仕様/機能とは直接関係ない慣例的つまり、JavaScript プログラマーの間では知っていて当然、知らないとちょっと恥ずかしい、ほぼ常識と化した記述方法あります。ここではそれについてのおさらいをしていきます。
変数/関数の命名規則
コーディングルールとしてまず抑えておきたいのは、変数と関数の命名規則でしょう。
これらの命名規則については、企業や開発チームなどで独自の細かい規定がある場合がありますが、一般的な JavaScript コードの記述においては、以下のようなものがあります。
キャメルケース
変数名/関数名を小文字ではじめ、二番目の単語の最初のアルファベットを大文字で記述する方法です。
名前中の大文字がラクダのコブのように膨らんでみえることからキャメルケースと呼ばれます。
コンストラクタ 関数
関数名の基本はキャメルケースですが、そのまま関数として使用するものなのか、new で呼び出されてインスタンスを生成して使用するものなのか区別するために、コンストラクタ関数の名前の先頭は大文字とします。
this
オブジェクト自身のインスタンスを指す this を保持する変数名は慣例的に that 、self、me といった変数名が使用されますが、最近の JavaScript 本では that として紹介されているのが多いようですので、とりあえず that としておけば文句は言われないでしょう。
定数
定数は大文字で記述し、単語の間を _ (アンダースコア) でつなぎます。
余談ですが、上の例を見て「const じゃなくて var かよ」と思った人、const 文は JavaScript 1.5 での Mozilla の 独自拡張で、ECMA-262 標準には含まれていません。よってすべての Web ブラウザーで動作するとは限りませんよ。
プライベート変数
オブジェクトの内部で使用している変数で、外部から使用されたくないものについては _ (アンダースコア) を前につけます。
変数名と関数名を区別するための工夫
変数名と関数名には、基本的にキャメルケースを使用しますが、そうすると関数と変数の見分けがつかなくなってしまう場合があります。
そういった状況を避ける工夫としては、関数の場合は get、set などの動詞を必ず前にもってくるようにするか、変数の場合はキャメルケースではなく単語のつなぎ目を _ (アンダースコア)にするのも悪くないアイディアです。
関数名、変数名には英数字、ほか_ (アンダースコア)、$ マークも使用可能ですが、 $ マークは jQuery 等、外部のライブラリなどで使用されている場合があるので、$ マークそのものは上書きしないほうが良いでしょう。
また $ マークは、HTML のエレメントのインスタンスを取得するための getElementById メソッドの短縮形として使用されることが多いようです。
以下のような使われ方をしているソースをよく見かけます。
//エレメントid okButton のインスタンスの取得
var okButton = $id(“okButton”);
//$id 関数の実装
var $id = function (id) { return document.getElementById(id); };
メモ : 日本語の変数名/関数名について
ECMA Script の v3 からは Unicode がサポートされており、そのため日本語の変数名、関数名も使用できるようになっています。
とはいえ ECMA Script v3 をサポートしていない古い Web ブラウザーや (※そうなると、そもそも Unicode でダメってはなしになりますが)、日本語を知らない国の開発者が読めないということもあるので使用しないほうが良いでしょう。
実は、変数や関数の名前に日本語を使ってはいけない決定的な理由というものを探してみたのですが、「他国の人間が読めない」、「テキストのエンコードを Shift_JIS とかにした場合問題が発生する」くらいしか誰に聞いても出てこなかったというのがあります。
逆に言うと、それらを気にしなければ使っても問題ないような気がしますが、日本語の関数名や変数名が使われているプログラムは個人的にはあまり格好良くない気がするので使わない方が良いような気がします。
もっとも、教材とか、ネタとしてはいいかもしれませんが。。
余談 : ローマ字表記の関数名と変数名なども、なかなかグっとくるものがあります。 gyoumu とか uriage、insatsu なんて名前のついたコードを見ると懐かしすぎて目頭が熱くなり、たぶんスーツ着た人がコード書いてんだろーなー、とか思います。
変数を宣言する場所
使用しているすべての変数が一か所で俯瞰できるようにするため、変数の値設定のタイミングによるエラー(※後述します) を避けるため、関数の先頭ですべての変数を宣言します。
JavaScript のスコープは関数単位となっており、他の言語のように for 文や if 文のブロック単位ではありませんので、変数の宣言をそれらコードブロック内で行うことには意味はありません。
また JavaScript の変数スコープをきちんと理解していない第三者がそれを見た場合、コードをメンテナンスする際に誤解が生じ、バグの原因ともなりかねないのでコードブロック内で変数を宣言するのは避けたほうが良いでしょう。
たとえば、以下の構文では、外側の for 文で使用している添え字 i が、内側の for 文で上書きされてしまうため正常に動作しません。
//添え字 i が上書きされてしまうため期待どおりの動作とならない
for (var i=0; i<5; i++) {
console.log("Outer loop " + i);
for (var i=0; i<10; i++) {
console.log("Inner loop " + i);
}
}
さらに、やっかいなことには var による変数の宣言は複数回行ってもエラーにはなりません。
var による変数の宣言だけであれば、それ以前に設定された変数の内容は変わることはありませんが、変数宣言と同時に初期値が設定される場合には当然のことながら上書きされてしまいます。
//複数回の var の宣言だけでは変数の内容は変わらない
var userName = "たろう";
var userName; //2 回目の var による宣言
console.log(userName); //たろう
var userName = "ぢろう"; //上書きされる
console.log(userName); //ぢろう
また逆に var による変数の宣言を行わないと、その上位の名前空間 (変数の名前が有効な範囲) に変数の宣言を探しに行き、そこで宣言させていないとさらに上位に…..、というように 巻き上げ られていき、最終的にはグローバルな(最上位の) 名前空間の変数として扱われます。
┌→ グローバル変数 messageString
│ として暗黙的に定義される
│ function parentFunc(){
└─────
┌────→ 定義されていない
│ function childFunc(){
│ 定義されていない
│ ↑
└───── messageString = "Hello;"
↓
定義されていない
}
}
グローバルな名前空間で、同じ名前の変数が定義されていた場合は上書きされてしまいます。
この意図しないグローバル空間の変数/関数の上書きを「名前空間の汚染」と呼びます。
以下のサンプルコードでは、グローバルな関数 showGreetings は、即時実行関数の中で内容が書き換えられ、返り値は “Hello” から “こんにちは” に変更されます。
//グローバルの関数
var showGreetings = function(){return "Hello" };
console.log(showGreetings()); //”Hello”
//即時実行関数
(function(){
showGreetings = function() {return "こんにちは"};
console.log(showGreetings()); //”こんにちは”
}());
console.log(showGreetings()); //”こんにちは”
以下のように、即時実行関数の中で、別途 showGreetings を宣言すれば、グローバルの関数 showGreetings は上書きされず、即時実行関数内の showGreetings 関数ともどもそれぞれの異なる値を返します。
//グローバルの関数
var showGreetings = function(){return "Hello" };
console.log(showGreetings()); //”Hello”
//即時実行関数
(function(){
var showGreetings = function() {return "こんにちは"};
console.log(showGreetings()); //”こんにちは”
}());
console.log(showGreetings()); //”Hello”
ただし、関数や変数の宣言を以下のように呼び出し後に記述すると、一見、関数ローカルでの定義が完了するまで、グローバルの関数として動作するように見えますが、実際はエラーが発生して動作しません。
//グローバルの関数
var showGreetings = function(){return "Hello" };
console.log(showGreetings()); //”Hello”
//即時実行関数
(function(){
console.log(showGreetings()); //エラーが発生
var showGreetings = function() {return "こんにちは"};
}());
console.log(showGreetings()); //”こんにちは”
これは変数 showGreetings がスコープ内にはあるものの、その内容が設定される前に使用されるためエラーが発生します。
上記のような問題を避けるためにも、関数内で使用しているすべての変数の宣言は関数の先頭で行うことをお薦めします。 (※)
(※) JavaScript のスコープの特性は理解しているものの、変数の使用範囲を示すために、あえてブロック内に変数を宣言することを否定はしませんが、第三者がソースを見たときに「この人、JavaScript のスコープも知らないのか?w」 と思われる点と、前述のサンプルのような、値の設定タイミングによるエラーの危険性を理解する必要があります。
変数の宣言の仕方
変数の宣言は、基本的には①のように各変数ごとに var キーワードを使用しますが、②のように , (カンマ) で区切ってまとめて定義することもできます。
どちらの宣言方法を使用するかについては、①のほうが「変数として宣言されていることが明確になり、可読性が高まるので良い」という人もいれば、②のほうが「var を省略することでデータ量を減らせるので良い」という人もおりまちまちです。
結論から言えば、きちんと関数の先頭で変数の宣言が行われてさえいればどちらの記述方法でも良いと思います。
しかしながら、可読性を上げたいときは①、従量課金制の通信環境など、データ量がコストに直結する、あるいは、帯域の細いネットワーク回線を使用していて、データを少しでも削る必要がある場合などは②にするなど、使い分けるのが良いかもしれません。
名前空間を汚染しないための工夫
前述の「変数を宣言する場所」でも書いたとおり、関数内で宣言されていない変数は、その上位の名前空間に宣言を探しに行き、宣言がされていない場合はグローバルの変数として扱われます。
こうした意図しない名前空間で変数/関数が有効になり、他の同名の変数や関数を書き換えてしまうことを「名前空間の汚染」といいます。
たとえば、以下のようなコードを jQuery を使用しているプログラム中で動作されると jQuery は正常に動作しなくなります。
こうした名前空間の汚染を防止するためにはいくつかの方法があります。
“use strict” ディレクティブの使用
ECMA Script 5 から “use strict” ディレクティブを使用することで変数の宣言を強制することができるようになりました。
使い方は function 内に “use strict”; と記述するだけです。
“use strict” ディレクティブが使用されている関数内で、未定義の変数を使用するとエラーが発生し、変数が定義されていないことを教えてくれます。
即時実行関数の利用
メインで動作する JavaScript コードを即時実行関数で包んでしまいます。
具体的には以下のような感じです。
即時実行関数の (function(){ })(); あるいは (function(){ }()); 内で記述されたコードは、無名関数内に書かれているため他の名前空間に変数名も関数名も公開しません。よって、名前空間を汚染することはありません。
しかし、そのため、外部の名前空間から即時実行関数内の関数にアクセスすることもできなくなっています。
たとえば、前述のサンプルコードの即時実行関数内に記述されている otherFunction 関数は、外部から使用することはできません。
よって以下のような、エレメントの onClick アトリビュートに関数名を指定するといった昔懐かしい方法でのイベントハンドラの設定も行えません。
//otherFunction 関数にアクセスできない
<input id="selfExplodeButton" type="button" onClick="otherFunction()">
即時実行関数 (無名関数) 内の関数を HTML エレメントのイベント ハンドラとして登録するには、以下のように即時実行関数内から addEventListener メソッドを使用して関数を登録します。なお、Internet Explorer 8 以前の IE には addEventListener メソッドがないので attachEvent メソッドを使用します。
document.getElementById("selfExplodeButton").addEventListener("click",otherFunction, false);
HTML エレメントのイベントの登録は、即時実行関数の中から行えますが、他の名前空間の関数に、内部の関数を使用させることはできません。
つまりこの方法では、ライブラリなどは作ることできません。
名前空間を極力汚染せずに、外部に対して関数を公開するには、名前空間を定義します。
オブジェクトを利用した名前空間の使用
JavaScript には名前空間を定義するための機能は用意されていませんが、オブジェクトを使用して同様のことが可能です。
たとえば、以下のようにオブジェクトを定義し、関数をメソッドとして定義すると、グローバルな名前空間には myUtil というオブジェクト名を 1 つのみ公開し、その下でいくつでも関数を公開できます。
内部の関数の呼び出し方は以下のようにします。
var resultA = myUtil.methodA();
var resultB = myUtil.methodB(“引数”);
オブジェクトの定義は、以下のように記述することもできます。
内部の関数の呼び出し方は同じです。
後者の記述方法では、myUtil の宣言を以下のようにすると、さらに安全にオブジェクトを定義することができます。
//myUtil がすでに定義されていればそれを使用し、
//定義されていなければオブジェクトを作成する
var myUtil = myUtil || {};
{} (中括弧、ブラケット) の書き方
JavaScript でコードブロックを定義するのに使用されるのが {} (中括弧、ブラケット)です。
JavaScript で {} を記述する場合は、中括弧の開始括弧 { の前で改行しない記述をお薦めします。
これは、JavaScript における行末への暗黙的 ; (セミコロン) の影響を避けるためです。
JavaScript の構文は ; (セミコロン) で終了することになっていますが、構文の最後が行末である場合 ; を記述しなくても動作します。
これは、「; を書かなくとも良い」 ではなく、実は JavaScript のランタイムが、「ったく、しょーがねーなー、このド素人が!」とか言いながら (※聞こえませんがたぶん言ってます。) こっそりと実行時に ; を追加しているから動作しているのです。
このなんとなく気の利いた JavaScript のセミコロン自動挿入は、状況によってはエラーを引き起こします。
たとえば、以下の左右の関数は return の後ろに改行が入る以外はまったく同じコードです。しかし左側の関数は正しく動作するものの、右の関数はエラーが発生し正しく動作しません。
//正常に動作
function userInfo(){
return {
firstName : "ひろし",
lastName : "山田",
age : 23};
}
//エラーが発生する
function userInfo(){
return
{
firstName : "ひろし",
lastName : "山田",
age : 23};
}
原因は return 文の後ろに暗黙的にセミコロンが挿入され、構文が途中で終了しているとみなされるためです。
上記のようなエラーを避けるためにも、中括弧の開始括弧 { の前で改行しない記述をお薦めします。
また、for 文や if 文については、以下のように中括弧を省略した記述も可能ですが、可読性や、2 つ以上の処理を追加した際にはいずれにせよ中括弧の記述が必要になるものの、詳しくない人間が中括弧を書かないまま処理を追加してしまう危険性あり、メンテナンス性も劣るため、必ず中括弧を記述するようにします。
DOM (Document Object Model) アクセスについて
DOM は、HTML ドキュメントをアプリケーションから利用するための API で W3C で勧告されています。
Web ブラウザー上に表示されるドキュメントやボタンやテキストボックスから情報を取得したり、操作を行うには、DOM を使用して HTML エレメントのインスタンスを取得する必要があります。
DOM を使用した HTML エレメントのインスタンスを取得する一般的な方法は以下のとおりですが、HTML エレメントへのアクセスは、目的のエレメントを探すため DOM ツリーを検索するので、非常にコストがかかります。
たとえば、以下のコードは最悪の部類に入るアンチパターンのサンプルコードです。このコードではループ毎に DOM ツリーを検索して見つかったエレメントの子ノードの innerText プロパティを設定しています。さらには、for 文の終了回数を調べる際、同じくDOM ツリーを検索し、見つかったエレメントの子ノードの数にアクセスしています。
上記のアンチパターンの例では getElementById メソッドを使用していますが、ループ内で jQuery のセレクターを使うなどするとさらに状況は悪くなります。
このような処理は、取得したエレメントのインスタンスを変数に格納し、その変数を使用することで毎回の DOM ツリーの検索を減らし、処理効率を上げることができます。
以下は、前述のアンチパターンのコードを効率よく処理できるよう修正したものです。
上記のコードでは、繰り返しアクセスされるエレメントのインスタンスを変数に格納しているため、for 文中では、DOM ツリーの検索は行われず効率よく動作します。
DOM を使用して HTML エレメントへのアクセスする場合、2 回以上アクセスされる可能性のあるものについては、そのインスタンスを変数に格納しておくことを心がけてください。
ここまでのまとめ
JavaScript には、言語の仕様/機能とは直接関係ない慣例的なコードの記述方法があります。
これら先人たちが培ってきたルールが生まれた背景、そしてそれに則ってコードを書く理由としては以下のものがあります。
可読性を高めるため
メンテナンス性を高めるため
意図しない動作を避けるため
他者に迷惑をかけないためのマナー
そして、これ以外の理由として、私が同セッションで紹介した真の理由は、手塩にかけて育てたコードが第三者に見られたときに「なにこのク〇コード? 超ダ〇いんですけど。ウケるwww」などと心無い誹謗を受けないようにするためです。(そうだっけ?)
これで完全とはいえないでしょうが、これらを意識してコードを書いていれば、それほどひどいことは言われないと思います。
次回も引き続き 第4回「ブラウザー勉強会」に『モテる JavaScript』フォローアップで「使われているとなんかカッコ良い JavaScript の機能」について書きたいと思います。
Comments
Anonymous
May 10, 2013
楽しんで読めました。ありがとうございます。次回にも期待してますAnonymous
May 14, 2013
ありがとうございます! 時間を捻出して書きたいと思います。