2010年01月06日

javascript: var v = a || b; の利点欠点と応用

var v = a || b;

評価式の利点欠点と応用(?)を考え始めたら、結局 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); // 入れてやる <- 当然代入まで行っていない




■ 利点は?
  • 値が返される
    (例 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 です


処理速度では?

// ■ 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));


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 の方が速いのは驚いた。 && の次の式の影響と言うより && の評価のための事前処理に時間が掛っていると見る方が自然か・・・。ちなみに == より === の方が速い。

その他欠点
  • 若干遅い
つまり、見通しが悪く(バグの温床)遅いとなると上記の利点で余程痛快なコードが書けないと利用価値としてはほぼゼロに等しい。実際、v = a || b; 程度しか見掛けないし。
ただ、今回は 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));


ちなみに上記では

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];

スッキリするこちらよりも速い。

posted by HiFa at 03:19 | 🌁 | Comment(0) | TrackBack(0) | JavaScript雑感 | このブログの読者になる | 更新情報をチェックする
>>> スパムコメントは消してますよん。 お互い無駄な労力は避けましょう。 <<<

この記事へのコメント

コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.seesaa.jp/tb/137514631
※言及リンクのないトラックバックは受信されません。

この記事へのトラックバック
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。