JavaScriptには、 自動セミコロン挿入 という機能があり、行末でセミコロンを省略しても、多くの場合文法的に問題ありません。 しかしながら、 JavaScript: The Good Parts などで指摘されているように、自動セミコロン挿入は有害な機能であるため、JavaScriptのステートメント末尾には必ずセミコロンを付与するというのがフロントエンドエンジニアの共通認識だと思っていました。1
ところが、 Bootstrap に含まれるJavaScriptコードを見てみると、基本的にセミコロンが使用されていません。 調べてみると、どうも世の中にはJavaScriptのステートメント末尾にセミコロンをつけない派というのが存在するらしいことがわかってきました。
自動セミコロン挿入(Automatic Semilocon Insertion)
本題に入る前に、自動セミコロン挿入とはどういうものかについて簡単に説明しておきます。
自動セミコロン挿入(ASI)の詳細なルールは、 ECMAScriptの仕様(ECMA-262 5.1 Editionの7.9節) に記述されていますが、要約すると次のようになります。2
- 次の条件を満たすとき、プログラムが文法的に許可されないトークン3を含むならば、セミコロンが挿入される。(a) 当該トークンの前に改行がある、または、(b) 当該トークンが閉じ中括弧である。
- ファイル末尾に到達したとき、セミコロンを挿入しなければプログラムを解釈できないならば、セミコロンが挿入される。
- Restricted Productionが出現したときに、文法既定で"[no LineTerminator here]"と記述されている場所に改行があるならば、セミコロンが挿入される。
具体例を挙げましょう。次のような内容のJavaScriptファイルがあるとします。
{ 1
2 } 3
これは、自動セミコロン挿入により、次のようになります。
{ 1
;2 ;} 3;
1 2というトークンの列は文法的に許されませんが、2というトークンの前に改行があるため、セミコロンが挿入されます。 また、2の直後に閉じ中括弧が来ることも文法的に許されませんが、セミコロンが挿入されることにより文法的に許可されるコードになります。 これらは、(1)のルールで説明できます。また、このファイルの末尾に到達した状態では、文法的に許可されない状態になるので、(2)のルールによりセミコロンが挿入されます。4
Restricted Production
3番目のルールにあるRestricted Productionについては、詳しく説明が必要でしょう。
JavaScriptでは、基本的にトークンは改行、スペース、タブなどの最低1個の空白文字で区切られ、空白文字の数や種類を増やしたり減らしたりしても文の意味は変わりません。これは、CやJavaなどのいわゆるC言語系と言われるようなプログラミング言語に共通する特徴です。
しかしながら、JavaScriptには一部例外があり、return, throw, break, continue, ++, --といったトークンの直後または直前の区切りの空白に限っては、その中に改行が含まれる場合と含まれない場合とで、文の意味が変わるのです。これら6種類の要素がRestricted Productionと呼ばれるものです。ECMAScriptの文法定義で、Restricted Productionの前後には、"[no LineTerminator here]"という文言が書かれています。ここに改行が入ると、自動的にセミコロンが挿入されるのです。つまり、
return
a + b;
このコードが、
return;
a + b;
このようになります。
ASI Scanning Test Grounds というサイトで、これらのいささか奇妙な規則をどの程度理解できているかが試せるので、お暇な方はチャレンジしてみてください。
ASIの害
ASIについてよく挙げられる害のひとつとして、前述の例で上げた、return直後の改行で、(ASIを理解していないと)意図しない動作をしてしまうという点があります。
たとえば、オブジェクトを関数から返したいときに、
return {
a: 1,
b: 2,
c: 3
};
であれば、正しくオブジェクトが返るのですが、
return
{
a: 1,
b: 2,
c: 3
};
このようにスタイルを変えてしまうと、ASIにより文が区切られてしまうため、関数の返り値はundefinedとなります。 なお、この害は、行末にセミコロンを付加するかどうかとは関係なく、ASIの仕様を知らなければハマってしまう問題です。
また、もうひとつ実際に起こりそうな害、そして、ステートメント末尾のセミコロンを省略するコーディングスタイルで起きる害として、()が意図せず関数呼び出しとして解釈されてしまう問題があります。
JavaScriptでは、名前空間やブロック構文のような仕組みが用意されていないため、グローバル名前空間の汚染を防ぐために、次のようなテクニックがよく使われます。
(function() {
// この中に書けばグローバル空間に影響を与えることなくコードを実行できる
var x, y, z;
})()
ここで、次の一見問題がなさそうなコードを見てみましょう。
a = b + c
(function() {
var x, y, z;
})()
このケースは、ASIによってセミコロンがcの後に挿入されるように思えるかもしれませんが、そうはなりません。 なぜなら、cの後の()は関数呼び出しと解釈することが可能だからです。この場合、関数オブジェクトをパラメータに取る関数呼び出しと解釈されます。ステートメント末尾に常にセミコロンを付けるスタイルであれば、このようなことは起きません(セミコロンを付け忘れない限りは)。
セミコロンにまつわる論争
さて、前述の通り、Bootstrapは、ステートメント末尾にセミコロンを付与しないスタイルを取っています。Bootstrapのクリエイターの一人である、Jacob Thorntonはこの理由について ブログで説明しています。 5
その主張は、
- JavaScriptがそもそも多様な書き方を許す言語である(セミコロン云々を抜きにしても)。
- ASIに頼れば、ステートメント末尾は \n のみで十分である(セミコロンを付加するのは冗長)。
- 改行後の(function() {...})() と結合して関数呼び出しと認識される件については、 変わりに !function() { ... }() と記述すれば回避可能。
3番目の理由ですが、たとえば先程の例で言うと、
a = b + c
!function() {
var x, y, z;
}()
こう書けばcと()が繋がることなく、その場で関数呼び出しを行うことができます。6
npmのクリエイターであるIsaac Z. Schlueterも セミコロン不要派 です。彼の意見では、JSコミュニティのリーダー達が、不用意にセミコロン省略への不安感を与え、JavaScriptの動作について嘘を教えているというのです。彼は、ステートメントの末尾にセミコロンを付与するのではなく、セミコロンが必要な箇所(括弧から始まる行など)でのみ、行の先頭にセミコロンを置くスタイルを提案しています。また、returnの後の改行でASIが起きる仕様については、常にセミコロン省略をする習慣を持てば問題にならないというのです。つまり、
return
7
このようなコードを見たときに、改行=ステートメント終了というスタイルが染み付いていれば、returnの後にトークンが続くことはおかしいと即認識できるようになるという主張です。また、人間が文章を読むときの視線誘導の観点からも、重要なもの(セミコロン)は右端ではなく、左端に置くべきである、とも言っています。
また、Restricted Productionsの問題があるので、いずれにせよASIへの正しい理解はJavaScriptを使っていく上で避けて通れない。それならば、いたずらにセミコロン省略への恐怖を煽って、なにも考えずにステートメント末尾にセミコロンをつけておけば問題ない、などと言うのではなく7、だれもがきちんとASIを理解してJavaScriptを使うよう促すべきだという考えを持っているようです。
一方で、JavaScript: The Good Partsの著者で、JS界のオピニオンリーダーであるDouglas Crockfordは、上記で提案されたような、! を文の区切りとして使うようなスタイルを 激しく批判しています 。彼は、著書の中でASIはJavaScriptの悪いパーツだと述べており、ステートメント末尾には必ずセミコロンを付加するのがいいスタイルであるとしています。また、リンク先のスレッドの例を見ればわかるように、改行がASIの必要条件なので、ASIに頼るコードは、ミニファイアーにかけて改行が除去されたときに壊れてしまう場合があります。8 Jacob Thorntonは、ASIまで含めてJSコードを完全にパースしないミニファイアは欠陥品であると非難しています。9
ここまで、ASIを積極的に利用するセミコロン省略に関する相対する主張を見てきましたが、では、ASIの元々の設計意図はどういうものだったのでしょうか?JSの設計者であるBrendan Eichは、 自身のブログ で、ASIはあくまでセミコロンを誤って書き忘れた場合の訂正措置であり、普遍的に改行に意味を持たせるような使い方をすれば、トラブルに巻き込まれるだろうと言っています。
で、どっちがいいの?
けっきょく、この論争は、(初期の設計意図はどうあれ)JavaScriptが2つの相反するスタイルを許容する言語であることに起因します。ここまで見たように、どちららのスタイルであっても動くコードが書けるのは事実なので、審美的な点を除けば、実用上、どちらかに決定的な優位があるというわけではないように思われます。もちろん、個人の好みがどうあれ、他人と共同作業しているコードであれば、そのプロジェクトのスタイルに合わせるべきであることは、言うまでもありません。
筆者個人としては、デザイナーなどプログラミングに習熟しているわけではない人に教えるときに、ASIの細かいルールまで詰め込ませるのは無理がある気がしますし、それなら、「ステートメント末尾にはセミコロンを付けること( そしてreturnで値を返すときは決して直後に改行を入れないこと! )」という単純なルールのほうが、役に立つと思います。
一番いいのは、このようなどうでもいい問題に悩まされないよう、 CofeeScript を使うことかもしません。
- Hacker Newsでのアンケート では、90%がセミコロンを付ける派でした。 ↩
- この要約は、JavaScript Semicolon InsertionEverything you need to know から引っ張ってきました。 ↩
- 文法上の最小の構成要素、1 2 3 "abc"といった数値や文字列リテラル、if,while,for,functionといったキーワード、+,-,=などの記号はすべてトークン ↩
- 文法的に許されるかどうかの判定は、ECMAの定義にあるBNFという文法を形式的に定義したものに照し合わせればわかるのですが、ここで許可されないと言っているのは、要するにステートメント末尾にセミコロンが不足しているためです。 ↩
- Jacob Thorntonのブログは現在停止中のようですが、GitHubにソースが残っています。https://github.com/fat/wordsbyf.at/blob/master/articles/2011-10-31-i-dont-write-javascript.txt ↩
- このような書き方は、実際のJSコードでたまに見られますが、この記事を読むまで、なぜこのような奇妙なことをするのか筆者は理解できませんでした。 ↩
- 実際、それだけでは、return直後の改行を防げない ↩
- Douglas Crockfordは、JSMinというミニファイアの作者です。 ↩
- さすがにそれは少し酷なのでは、という気がしますが…。 ↩