評価式の利点欠点と応用(?)を考え始めたら、結局 switch-case も侮れんなぁ。という結果。ちなみに式は式だけど「評価式」と呼んで良いのかな?
利点・欠点、応用の前にまず確認。
// ■ 動作確認
alert(3 || 4); // 3
alert(3 && 4 || 5); // 4
alert(3 < 4 || 5); // true
// ■ false 扱いに関して
var o = {};
alert(0 || '' || null || false || undefined || void(0) || o.noprop || '全部 false 扱い'); // 全部 false 扱い
alert(undefined || 0); // 0 <- 注意
alert(0 || undefined); // undefined <- 注意
// ■ if と等価な処理
function test(){
alert('処理');
}
if(!o.noprop) test(); // 処理
o.noprop || test(); // 処理
// ■ くどいけど確認
o.noprop || (o.noprop = '入れてやる');
alert(o.noprop); // 入れてやる
o.noprop || (o.noprop = 'すでに入っていると?');
alert(o.noprop); // 入れてやる <- 当然代入まで行っていない
alert(3 || 4); // 3
alert(3 && 4 || 5); // 4
alert(3 < 4 || 5); // true
// ■ false 扱いに関して
var o = {};
alert(0 || '' || null || false || undefined || void(0) || o.noprop || '全部 false 扱い'); // 全部 false 扱い
alert(undefined || 0); // 0 <- 注意
alert(0 || undefined); // undefined <- 注意
// ■ if と等価な処理
function test(){
alert('処理');
}
if(!o.noprop) test(); // 処理
o.noprop || test(); // 処理
// ■ くどいけど確認
o.noprop || (o.noprop = '入れてやる');
alert(o.noprop); // 入れてやる
o.noprop || (o.noprop = 'すでに入っていると?');
alert(o.noprop); // 入れてやる <- 当然代入まで行っていない
■ 利点は?
- 値が返される
(例 f = a || b;) - 分岐
- 式なので柔軟に挿入できる
(例 alert(…) や Function(…) の … に if 文は入れられないが式はOK) - 1 ? 1 : 0 と同じ感覚だけどより柔軟
- false 扱いの値に気を付けなければならない
(当然、処理を挟んだ場合その処理が何の値を返すかにも留意) - 一見、何をしているのか分かり辛い
(書き方次第?)
// ■ switch の代わり (※ alert の中に入れられる switch)
var check = 2;
alert(
(check == 1 &&
'1 です'
) ||
(check == 2 &&
'2 です'
) ||
(check == 3 &&
'3 です'
)
);// 2 です
// ■ でもこんな程度ならこっちのが分かりやすいしラク
alert(['0 です', '1 です', '2 です', '3 です'][check]);// 2 です
// ■ ただ、オブジェクトに突っ込むと switch で言う所の default が使えないが、評価式なら可能
var check = undefined;
alert(
(check == 1 &&
'1 です'
) ||
(check == 2 &&
'2 です'
) ||
(check == 3 &&
'3 です'
) ||
'不明です'
);// 不明です
// ■ 逆に alert を入れられる(ただし if(alert()); も可能)
var check = 2;
check = (
(check == 1 &&
(
alert(1) || 1
)
) ||
(check == 2 &&
(
alert(2) || 2
)
) ||
(check == 3 &&
(
alert(3) || 3
)
) ||
'不明です'
);
alert(check);// 2 <-- 2回
// ■ 分岐処理の結果を変数に代入することなく (してもいい) 直接利用できる
function test(v){
alert('それは ' + v);
}
var check = 2, r = '';
test(
r = // <-- あってもなくても
(check == 1 &&
'1 です'
) ||
(check == 2 &&
'2 です'
) ||
(check == 3 &&
'3 です'
) ||
'不明です'
); // それは 2 です
alert(r); // 2 です
var check = 2;
alert(
(check == 1 &&
'1 です'
) ||
(check == 2 &&
'2 です'
) ||
(check == 3 &&
'3 です'
)
);// 2 です
// ■ でもこんな程度ならこっちのが分かりやすいしラク
alert(['0 です', '1 です', '2 です', '3 です'][check]);// 2 です
// ■ ただ、オブジェクトに突っ込むと switch で言う所の default が使えないが、評価式なら可能
var check = undefined;
alert(
(check == 1 &&
'1 です'
) ||
(check == 2 &&
'2 です'
) ||
(check == 3 &&
'3 です'
) ||
'不明です'
);// 不明です
// ■ 逆に alert を入れられる(ただし if(alert()); も可能)
var check = 2;
check = (
(check == 1 &&
(
alert(1) || 1
)
) ||
(check == 2 &&
(
alert(2) || 2
)
) ||
(check == 3 &&
(
alert(3) || 3
)
) ||
'不明です'
);
alert(check);// 2 <-- 2回
// ■ 分岐処理の結果を変数に代入することなく (してもいい) 直接利用できる
function test(v){
alert('それは ' + v);
}
var check = 2, r = '';
test(
r = // <-- あってもなくても
(check == 1 &&
'1 です'
) ||
(check == 2 &&
'2 です'
) ||
(check == 3 &&
'3 です'
) ||
'不明です'
); // それは 2 です
alert(r); // 2 です
処理速度では?
// ■ switch-case
var cnt = 1, check = 3;
var t = (new Date()).getTime();
while(cnt <= 2000000){
switch(check){
case 1:
cnt++;
break;
case 2:
cnt++;
break;
case 3:
cnt++;
break;
}
}
// ■ switch-case(式)
alert(cnt + " " + ((new Date()).getTime() - t));
var cnt = 1, check = 3;
var t = (new Date()).getTime();
while(cnt <= 2000000){
switch(true){
case check == 1:
cnt++;
break;
case check == 2:
cnt++;
break;
case check == 3:
cnt++;
break;
}
}
// ■ 評価式
alert(cnt + " " + ((new Date()).getTime() - t));
var cnt = 1, check = 3;
var t = (new Date()).getTime();
while(cnt <= 2000000){
check == 1 && cnt++;
check == 2 && cnt++;
check == 3 && cnt++;
}
// ■ if
alert(cnt + " " + ((new Date()).getTime() - t));
var cnt = 1, check = 3;
var t = (new Date()).getTime();
while(cnt <= 2000000){
if(check == 1) cnt++;
if(check == 2) cnt++;
if(check == 3) cnt++;
}
alert(cnt + " " + ((new Date()).getTime() - t));
var cnt = 1, check = 3;
var t = (new Date()).getTime();
while(cnt <= 2000000){
switch(check){
case 1:
cnt++;
break;
case 2:
cnt++;
break;
case 3:
cnt++;
break;
}
}
// ■ switch-case(式)
alert(cnt + " " + ((new Date()).getTime() - t));
var cnt = 1, check = 3;
var t = (new Date()).getTime();
while(cnt <= 2000000){
switch(true){
case check == 1:
cnt++;
break;
case check == 2:
cnt++;
break;
case check == 3:
cnt++;
break;
}
}
// ■ 評価式
alert(cnt + " " + ((new Date()).getTime() - t));
var cnt = 1, check = 3;
var t = (new Date()).getTime();
while(cnt <= 2000000){
check == 1 && cnt++;
check == 2 && cnt++;
check == 3 && cnt++;
}
// ■ if
alert(cnt + " " + ((new Date()).getTime() - t));
var cnt = 1, check = 3;
var t = (new Date()).getTime();
while(cnt <= 2000000){
if(check == 1) cnt++;
if(check == 2) cnt++;
if(check == 3) cnt++;
}
alert(cnt + " " + ((new Date()).getTime() - t));
ie7,ff3 : switch-case << if < 評価式 ≒ switch-case(式)
sf4,ch3 : switch-case < if ≒ 評価式 < switch-case(式)
op10 : switch-case < 評価式 < if < switch-case(式)
んー惜しい。ie, ff 以外では if と同等かそれより早いのに、 ie, ff では if より遅い・・・。当然内容にもよるが「case で式を使うよりはいい」という程度。switch-case がやたら早いのは、evaluate というより seek なのだろうか。なにより ie, ff で if の方が速いのは驚いた。 && の次の式の影響と言うより && の評価のための事前処理に時間が掛っていると見る方が自然か・・・。ちなみに == より === の方が速い。
その他欠点
- 若干遅い
ただ、今回は check が global なので local にすれば switch に近づくかも。当然 if も速くなる。
気持ちとしては、分岐と値を返すまでがアトミックぽくて、かつ柔軟な所がかなり気に入ってるんだけど。欠点も大きい。
なんか目の覚める利用法ってあるのかな?
ところで、if と比べて switch-case がやたら速いのが気になったので、object を使った評価との速度を比べてみると。
// switch-case
var cnt = 1, check = 3;
var t = (new Date()).getTime();
while(cnt <= 2000000){
switch(check){
case 0:
cnt += 0;
break;
case 1:
cnt += 0;
break;
case 2:
cnt += 0;
break;
case 3:
cnt += 1;
break;
}
}
alert(cnt + " " + ((new Date()).getTime() - t));
// object 1
var cnt = 1, check = 3;
var t = (new Date()).getTime();
var o = [0, 0, 0, 1];
while(cnt <= 2000000){
cnt += o[check];
}
alert(cnt + " " + ((new Date()).getTime() - t));
// object 2
var cnt = 1, check = 3;
var t = (new Date()).getTime();
while(cnt <= 2000000){
var o = [0, 0, 0, 1];
cnt += o[check];
}
alert(cnt + " " + ((new Date()).getTime() - t));
var cnt = 1, check = 3;
var t = (new Date()).getTime();
while(cnt <= 2000000){
switch(check){
case 0:
cnt += 0;
break;
case 1:
cnt += 0;
break;
case 2:
cnt += 0;
break;
case 3:
cnt += 1;
break;
}
}
alert(cnt + " " + ((new Date()).getTime() - t));
// object 1
var cnt = 1, check = 3;
var t = (new Date()).getTime();
var o = [0, 0, 0, 1];
while(cnt <= 2000000){
cnt += o[check];
}
alert(cnt + " " + ((new Date()).getTime() - t));
// object 2
var cnt = 1, check = 3;
var t = (new Date()).getTime();
while(cnt <= 2000000){
var o = [0, 0, 0, 1];
cnt += o[check];
}
alert(cnt + " " + ((new Date()).getTime() - t));
ちなみに上記では
ie7 : object1 ≒ switch-case <<< object2
ff3 : switch-case < object1 <<< object2
sf, ch, op : object1 < switch-case <<< object2
object2 が遅いのは当然だけども(つまり関数化して呼び出す場合、関数内で定義しているとかなり遅くなる) object1 も scope によって異なってくるはず。コードによって結果が変わるのは当たり前だし、呼び出される頻度によってどちらが良いとは言えない。ただ、敢えて global 汚してまで object にするより、メモリも喰わない switch-case を使った方が良い場合ってありそうだ。何にせよ、調べた中では switch-case はオブジェクトプロパティ参照の次に速い。ff では逆転すらしている。
まあ、global をあまり汚さず、object でって事なら定義用オブジェクトに関数用のプロパティ入れたり、関数オブジェクト自身に持たせるなど方法はあるけど。
例えば、上記速度比較に合わせて書くと
var o = [0, 0, 0, 1];
o.method = function (check, cnt){return cnt += this[check];}
とか
function f(check, cnt){return cnt += arguments.callee[check];}
f[0] = 0;
f[1] = 0;
f[2] = 0;
f[3] = 1;
とか?
ちなみに1つめは繰り返し呼ぶなら var rfn = o.method; などとして rfn を使った方が速くなるのではないかと思うし、2つめもドットが余分に付いている分遅くなる。ただ、抽象化を気にしないのなら、この場合は arguments.callee ≒ f なので f に置き換えてやれば結果も異なってくると・・・。
ということで、2つめは冗長な入れ方だけど
function f(check, cnt){return cnt += arguments.callee.ary[check];}
f.ary = [0, 0, 0, 1];
スッキリするこちらよりも速い。
この記事へのコメント