JavaScriptでは、初見の人にはさっぱりわからないけれども、ある程度慣れた人は当たり前に使うイディオムが結構たくさんあります。知ってしまえば何てことはないので、私の知っている限りのイディオムとその意味を解説します。
(7/3追記: twitter等で教えていただいた内容を追加しました)
+v (数値化)
var v = "123";
console.log(+v + 100) // 223
console.log(v + 100) // 123100
vを数値化する方法では最もメジャーです。parseFloat(v) に比べて高速なのに加えて、parseFloatとは細かい挙動が異なります(例えば空文字列の場合、parseFloatならば NaN になりますが、 +v の場合はゼロになります)。必ず数値になることが保証されており、文字列などで数値化出来ない場合はNaNが返ります。
v - 0 (数値化)
var v = "123";
console.log((v - 0) + 100) // 223
console.log(v + 100) // 123100
こちらも数値化です。+v に比べてあまり使われることはありませんが、初見でもある程度意味が理解しやすいメリットはあるかもしれません。個人的な印象ですが、+v が大変メジャーなので v - 0 を使うよりは +v を使ったほうが良いかと思います。
v + "" (文字列化)
var n = 123;
console.log(n + 123); //246
console.log(n + "" + 123); //123123
文字列化するためのイディオムです。頻繁に使われますが、パッと見れば意味がわかるかと思います。
ただ文字列化は、あまりよろしくない文字列化のコードを書く方が多い印象があります。例えば、
var n = 123;
n = n.toString();
というコードがありますが、これは実行時の最適化を阻害する要素の多いコードであり、可読性が同程度であるv + ""を使うことをおすすめします。
他にも稀にみるのが String()
です。
var n = 123;
n = String(n);
これは速度的には問題がないですが、よく間違えて new String(n)
と書く人がいて、しかもnew String
でも一見正しく動いてしまう問題があるので、積極的に避けるべき書き方だと考えております。new String
で作られたStringオブジェクトは、そもそもObjectでありStringではありません。このObjectを文字列として評価しようとする際には、毎回内部的にtoString
関数(もしくはvalueOf
関数)が呼ばれてObjectからStringに変換されることになり、無駄の極みとなります。
String.prototype.valueOf = function() { return "aaa"; };
var n = 123;
var strobj = new String(n);
var str = String(n);
console.log("[" + strobj + "]"); // [aaa]
console.log("[" + str + "]"); // [123]
というわけで、文字列化する場合は v + ""
を使うのが大抵の場合望ましいのではないかと思います。
if(v != null) (nullとundefinedのチェック)
var o = { value: 0 };
if(o.value != null) { console.log("value property exists"); }
これは、さらっと書かれているので見逃しがちですが、nullとundefinedをチェックする大変重要なイディオムです。ある程度JavaScriptに慣れてくると、プロパティの存在確認をbooleanの自動変換に頼って書きがちになります。例えば
if(o.value) { ... }
という感じですね。valueがオブジェクトなどであればこれで問題ないのですが、ここのvalueが数値や文字列を取る場合には、たまたまそのvalueが0や空文字列であった場合にそれを検知できず、発覚しにくいバグとして残ってしまいます。
if(v != null)
で比較した場合、vがnullの場合とundefinedの場合のみ弾き、vが0の場合や空文字列の場合は引っかからずに通します。このイディオムで書かれている場合は、たいていそのことを意識して書かれていることがほとんどですので、決して if(v != null)
を if(v)
と書き換えないように気をつけましょう。vがたまたまゼロの場合などにしか発覚しない、大変面倒なバグになってしまいます。
(function(){ ... })(); // スコープ化
;(function() {
var x = "hello world";
console.log(x);
})();
ソースコードの最初と最後にオマジナイのように書かれる、わからない人にとっては謎のfunction文です。JavaScriptは関数の中に書かない変数宣言は全てグローバル変数として扱われてしまうので、グローバルスコープを汚さないように関数式でコード全体を囲っています。
関数式を宣言し、その関数を実行する処理を全部まとめて書いてしまっていますので、わかりやすく書くと
var f = function() {
var x = "hello world";
console.log(x);
};
f();
というようなコードを書いているのと同じです(この場合、fがグローバル変数になってしまっていますが)。なぜfunctionの前に括弧「(」があるかというと、これがないと関数式ではなく普通の関数宣言だと構文解析されてしまい、その結果構文エラーに成ってしまうためです。
一番最初のセミコロンは無くても大丈夫です。しかし、自分の知らないところでライブラリ等でファイル結合された場合、直前のファイルの最後にセミコロンが抜けていると構文エラーになってしまうので、念の為にセミコロンをつけた方が良いかもしれません。
なお、この関数のスコープ化にはいくつか流派があります。興味のあるかたはこちらをどうぞ→ http://blog.niw.at/post/25330545602
&& (if文の省略)
var o = { f: function() { console.log("hello world"); } };
o && o.f && o.f();
&& 演算子は、C++Javaなどでは boolean型にしか使えないですが、JavaScriptではどのような型にでも使えます。それを利用して、if文を書かずにオブジェクトの存在判定をするときに大変頻繁に使われるイディオムです。
例えば上記の例を if 文を使って書くと、以下のようになります。
var o = { f: function() { console.log("hello world"); } };
if(o) {
if(o.f) {
o.f();
}
}
このように冗長なif文を書かないために、&&を使って短縮しているわけです。特に関数の先頭などで、オブジェクトが特定のプロパティを持っているのか確認し、持っている場合はそれを実行する、というような処理をするときに頻繁に使われます。
中に代入を書く時は、代入を括弧でくくらないとパースエラーになるので気をつけてください。
o && o.options && o.options.players > 50 && (massiveFlag = true);
なお、このイディオムは数値や文字列、オブジェクトが内部で暗黙にbooleanに変換されるのを利用しております。数値の0やNaN、空文字列、null、undefinedがfalseに変換されますが、このイディオムを自分が使う場合は予期せぬ自動変換を避けるためにも null や undefined のチェックに留めておくのが良いと思います。
|| (if文の省略)
こちらも if文 の省略になります。
var v = (o && o.f) || function() { console.log("hello world"); };
v();
&&と同じように、if文を省略して書くときに使われます。変数の代入時にundefinedを避けるのに使われるのが多いです。
こちらをif文で書くと、
var v;
if(o) {
if(o.f) {
v = o.f;
}
}
if(!v) {
v = function() { console.log("hello world"); };
}
v();
となります。&&と同じように使えるので、両者が同時に使われる状況は多いです。
こちらも暗黙の型変換を利用しているので、どの値がtrueになり、どの値がfalseになるのかはきちんと把握されると良いと思います。
なお、&&や||を使うとif文を使うより速くなるかというと、スマートフォンにおいては昔はその傾向があったのですが、今ではほとんど差がなくなっています。可読性を犠牲にしてまで使うイディオムではなくなりつつあるイメージです。
v = v || {}
function f(o) {
o = o || {};
...
}
関数の先頭などで、デフォルト引数の設定などに大変良く使われるイディオムです。例えばoがundefinedの時などに、デフォルト値として空のオブジェクトを設定する、というような用法で使われます。上記の||と同じなのですが、特にたくさん見るので別項目に分けました。
+v || 0 (数値化、かつNaN以外を保証)
var n = +v || 0;
vの数値化に失敗した場合に、NaNではなくてゼロにするイディオムです。vがゼロだった場合もゼロになるので安心です。
v | 0 (整数化)
var i = v | 0;
vを整数にします。JavaScriptではビット演算は整数型に対してしか適用出来ないので、それを利用して強制的に整数にするイディオムです。vが文字列の場合でも、まず数値化され、その後整数化されます。
なお、Math.floor
でも似たような処理になりますが、マイナス数値を扱った場合の挙動が違うので気をつけてください。
console.log(-2.1 | 0); // -2
console.log(Math.floor(-2.1)); // -3
あと、細かい話ですが、ビット演算子は演算子の優先順位を間違える人が大変多いので、数式の中で使う際には括弧で囲って書いたほうが良いと思います。
~~v (整数化)
console.log(~~(2.1)); // 2
v | 0
と同じように整数化するイディオムです。個人的な印象ですが、v | 0
の方がメジャーですし初見で理解できる可能性がまだ高いので、v | 0
を使う方が良いのではないかなと思います。似たようなイディオムで v >> 0
(ビットシフト)もありますね。
!! (論理化)
var b = !!v;
vの値をbooleanに変換するイディオムです。例えば空文字列、ゼロ、NaN、undefined、nullなどをfalseにし、それ以外をtrueにします。
実際は、true/falseを明確にしなければいけない状況はあまりないとは思うのですが、他言語からの移植などのライブラリなどでは散見されます。
if(v != v) (NaNチェック)
var v = +"a"; // NaN
if(v != v) { console.log("v is Not a Number!"); }
NaNチェックです。NaNはJavaScriptの中で、唯一自分自身との比較に失敗する値となっています(NaNとの比較は相手がどんな値でも必ず失敗する)。よって、数値型の場合でもゼロとNaNを比較する際にこのイディオムを使うことがあります。
しかし、言うまでもなく、これはisNaN関数と同じ処理になります。スピードが大変重要である局面でなければ、NaNチェックにはisNaNを使ったほうが、読んだ人は意図が通りやすいんじゃないかなと思います。
(追記: isNaN関数は、例えば"a"の文字列など、数値化した際にNaNに変換されるものもNaNと判定します。一方、v != v だと純粋にNaNのみを判定出来ます。数値以外の値に対しては、isNaNと v != v の動作が大きく違うのでご注意ください)
~array.indexOf() (配列の値探索)
var a = [1, 2, 3, 4, 5];
if(!~a.indexOf(0)) { console.log("0 is not in a"); }
if(!~a.indexOf(3)) { console.log("3 is not in a"); }
if(~a.indexOf(5)) { console.log("5 is in a"); }
indexOf限定のイディオムです。indexOf関数は、要素が存在しなかった場合に-1を返し、存在した場合には要素のindexを返します。簡単に言うと、失敗==-1、成功>=0、となります。
そこにビット否定を入れます。ビット否定でなぜそうなるかの詳細は省きますが、-1のビット否定は0になり、0を含むその他すべての値のビット否定は0以外になります。
よって、indexOfの返値にビット否定をかけた場合、失敗時(-1)の場合のみゼロ、成功時はゼロ以外となるので、それを利用して配列の中に要素があるかどうかを知ることが出来ます。
これは極めて可読性の悪いイディオムであり、かつ普通に a.indexOf(v) != -1
と比較した場合に比べて速度的に全く恩恵がないと言い切っても問題ないほどの差しかありません。一部のオープンソースプロジェクトで多様されているので、意味は知っておいたほうが良いかもしれませんが、自分で書くのはあまりおすすめしません。
ちなみに文字列の場合でも利用できますが、最近のブラウザではそもそもindexOfを使うより正規表現を使ったほうが高速だと思います。
var s = "Hello world";
if(/e/.test(s)) { console.log("e exists"); }
new Function("return this;")() (グローバルオブジェクトの取得)
var global = new Function("return this;")();
console.log(global.JSON);
グローバルオブジェクトの取得です。一般的にブラウザのグローバルオブジェクトはwindowで取得出来るのですが、例えばnode.jsなどではそれが利用できないので、両者で利用される環境を想定した場合にはこのようなイディオムで取得することになります。
以前はもっと簡単に
var global = function(){ return this; }();
で取得することが出来たのですが、Strict Modeでは関数内のthisがundefinedを返すようになるのでこの方法は利用できません。今のご時世、常にstrict modeがあることを前提にすべきだと思うので、new Functionを使った方法を使うようにするとよいと思います。
ちなみに蛇足ですが、なぜStrict Modeでもnew Functionの場合にはグローバルオブジェクトが入ってくるかについては、Strict Modeの伝搬などの細かい仕様を調べてもらえるとおわかりになると思います。
function() { return !this; }() (Strict Modeチェック)
var isStrict = function() { return !this; }();
現在strict modeかどうかをチェックするイディオムです。上記の、strict mode内ではthisがundefinedになることを利用しております。
しかし紹介しておいてなんですが、現在strict modeかどうかをチェックすることってめったにないと思いますね。
typeof(v) == "undefined" (undefinedチェック)
if(typeof(o) == "undefined") { console.log("undefined detected"); }
undefinedチェックで頻繁に見られる構文ですが、この構文ならではの意図があります。例えば同じようにundefinedをチェックしたいときに、
if(o === void 0) { console.log("undefined detected"); }
と書いてもundefinedのチェックは可能です(voidは何を引数にとってもundefinedを返す構文であり、===チェックはnullとundefinedを区別して比較します)。しかし、o自体がローカルスコープでもグローバルスコープでも宣言されていない場合、この書き方だとoの参照の時点でリファレンスエラーが起こってしまいます。
例えばよくあるのが、ブラウザが用意したビルトインオブジェクトの存在をチェックするときです。古いJavaScriptでは存在しないJSONオブジェクトが存在するかどうかを確認したいときに、ただ単純にJSONを参照してしまうとリファレンスエラーになってしまいます。かといって、window.JSONで確認すると、node.jsなどのwindowがない環境での動作ができなくなってしまいます。ただの存在チェックで、上記のグローバルオブジェクトの取得まで頑張るのはちょっとありえないので、そういう際にはこのtypeof方式をつかって存在確認をする方法がベストになるでしょう。
なおスピードを気にする方も多いかと思いますが、typeofは高速ですし、固定文字列同士の比較なのでそちらの速度もかなり速く最適化されるのが一般的です。あまり気にしすぎることはないと思います。
なお、undefinedをvoid 0
ではなくてundefinedと書いて参照するのは、内部的にはwindow.undefinedを参照することになるので、あまり好まれない書き方となります。さすがにwindow.undefinedの値が書き換えられることはないと考えてよいとは思いますが…。
v >>> 0 (符号なし整数化)
var n = 0x80000000;
console.log(n); // 2147483648 == 0x80000000
console.log(n | 0) // -2147483648
console.log(n | 1) // -2147483647
console.log((n | 1) >>> 0) // 2147483649
32ビットの整数値を、強制的に符号なしに変換するイディオムです。JavaScriptでバイナリを扱う人にしか役立たないと思います。
JavaScriptは数値はすべて浮動小数点型(float型)で扱われるのですが、ビット演算をする際には内部的にToInt32が呼ばれて32ビットの整数値に変換されます。この際の変換が符号付きで行われるので、一番上のビットが立ってしまった場合にはマイナスの値として処理されてしまい、その後の計算に支障を起こすことがあります。そういうときにこのイディオムを使うことによって、内部的にToUint32を呼ぶことによって符号なしの整数型に一度変換してやり、その値をその後の計算に利用することが出来るようになります。
ほとんど目にすることはないでしょうが、バイナリを扱ったり、極限までビット計算を効率化しようとすると、かなり高い頻度で扱うことになるでしょう。
もし追加で「これも…」というものがあれば、 Twitter の @tkihira までお知らせください。