JavaScriptのifのとか()の知らなかった話
事の切っ掛け
事の始まりはこれ。とあるコードをOnline YUI Compressorに通そうとしたときに新しいツールを紹介されたので、そっちに通して見たときの事。ちなみにそっちの圧縮サービスはこれ。-> Refresh-SF - Online JavaScript and CSS Compressor
if(a=="foo"||b=="bar") console.log("Bazinga!");
たとえばこんな箇所が、以下のようにされていた。
("foo"==a||"bar"==b)&&console.log("Bazinga!");
この結果を見て不思議に思ったのでconsoleでちょこちょこ触ってみたところ「ぁー成る程、こう書けるのか」と妙に納得した。
一行ifを&&で代用する?
このカラクリは、&&は左側の値がfalseの地点で評価を進めないという事故。従って、左側の中身の括弧内の評価がそもそもtrueにならない限り&&右側の関数の戻り値を評価しない為、結果として関数も呼び出されない、という事のよう。
ではこのminifyツールで同じく今度は普通の{}囲みのif文を与えたらどうなるのか試してみた。
if("foo"==a){
console.log("Bazinga!");
console.log("Bazinga,again!");
}
"foo"==a&&(console.log("Bazinga!"),console.log("Bazinga,again!")));
こうしたら無名関数で囲まれるのかな、と思ったら違った。
勿論私のようなヘッポコの想像したとおり、無名関数にしても通る。
"foo"==a&&(function(){
console.log("Bazinga!");
console.log("Bazinga,again!");
})();
この何もないところの()のカンマ区切りがホントの所どういう意味なのか? この辺についてそもそもどうしらべたものか分からないので適当にコンソールでつついていると、以下のようになるのが分かった。
- ()とは、直前に指定されているfunctionオブジェクトに引数を渡すためのものである。
- なにも指定されていない場合の()は、functionに渡している時同様引数の評価を行う。
- その場合の戻り値は、一番最後の引数の結果が戻ってくる。
例えば、何気なく即時関数を以下のように書いているけど、これは最初の()にfunctionオブジェクトを渡して評価する事で戻り値にそのまま戻ってくるfunctionに引数を渡す()を与える事で関数として実行される、という流れが見えてくる。
(function(){ console.log("bazinga!");})()
ここまで来るとなぜfunction(){}を()で囲むのか、とかが何となく見えてくる。
要するに、単なる()は関数への引数としての処理と同様に中身が評価され、その末端の結果が戻り値である為、それは変数の中身に収めた関数の参照と等価であり、()を付ける事でそれが関数として実行される、という事か。成る程、納得した。
(function(){ console.log("ready?");},function(){ console.log("bazinga!");})()
// >bazinga!
故に無意味ではあるがこういう事も出来るわけだ。
面白い。何気なく使ってたコード中の()だったり、即時関数の理屈がなんかかなりスッキリ理解出来た気がする。
三項演算子とかboolとかの扱い
そしてもう1箇所。実は某お題の解答を書いていたときのコードを試しに同じようにminifyしてみた時の結果。処理は1桁の素数かどうかの判定をするだけの関数。
//Original
function isPrime(a){
if (a%2==0||a%3==0||a%5==0||a%7==0) return true;
return false;
}
//Minified
function isPrime(r){return r%2==0||r%3==0||r%5==0||r%7==0?!0:!1}
//あれ?もしかしてこれでよくね?
function isPrime(r){return r%2&&r%3&&r%5&&r%7?!1:!0}
まずはそもそも 三項演算子を使えばスマートでしたね....と。ハズカチ。問題はその後の式。true,falseじゃなく0,1の否定を行ってる。これはなるほどなーと思った。確かにこうすれば確実に動作出来る上に、true,falseと書くより遥かに少ない文字数でスッキリ書ける。
false === false //true
false == 0 //true
false === 0 //false
false === !1 //true
false == !1 //true
でもこれ1度否定で評価する故処理コストとしては増えそうな気がしないでもないけど、その辺はベンチしても微々たるさしか出てこない気も。実際の所はよくわからんです。(ベンチしろ?ごもっとも) 少なくとも単に0を渡すのは厳密比較をパスしないので×。
その点言えばこの方法は非常にスマート。ただこの記法が可読性に繋がるか?というと正直全く逆行はしてるとは思う。1バイトでも小さくする事を求められているときに有用はのは確かだけども。 ちなみに文字数は変わらないものの、こういう書き方も出来ますな。
function isPrime(r){return !(r%2)||!(r%3)||!(r%5)||!(r%7)?!0:!1}
個人的にはこの書き方好き。これも応用ですな。0との比較なので、そのまま%したときに戻ってくる数値を!で否定してしまい、0ならtrueになる....とここまで書いていて気づきました。
function isPrime(r){return r%2&&r%3&&r%5&&r%7?!1:!0}
0をtrueとして、trueが最低1つという条件なのであれば、別にfalseの存在が1つでもあればパスできなくなる&&で結んでしまえば良い。結果は反転してしまうけど、それは結果の処理の中身を反転してしまえば良いだけ。じゃこう書けばいいぢゃん!すっげー!可読性なんてクソ食らえ! オリジナルと改行抜きで30バイト短いし、無駄な評価(0を否定、とか)が減った分理屈上でも処理がシンプルになった筈。読みにくいけど、得体の知れ無さがアップして好み。
そんなわけで、minifyツールに色々気づかされた次第。まだまだ知らない事だらけだけど、JavaScriptは面白いですね。