JavaScript

javascriptイディオム集

More than 1 year has passed since last update.

はじめに

イディオムは、ある目的を達成するために 慣習的に書かれるコード のことです。
他人のコードを見るときに戸惑わないようまとめました。

解説は極力無くしています。気になるものは、 参考 から辿ってみてください。
コメント は、イディオムを使用しない場合の書き方の例です。
タイトルに (嫌) が付いているのは、嫌悪する人が多いので極力ライティングで避けるイディオムです
推奨非推奨 はライティングする際にどれにするかの目安としました。(主観です)
動作環境は、es5以上もしくはes5-shim適用です。

イディオム

文字列を数値化

var x = '123';
var y = +x;     // 推奨
var y = x - 0;
var y = x * 1;
// var y = Number(x);  // 非推奨

数値を文字列化

var x = 123;
var y = x + '';  // 推奨
// var y = x.toString(); // 非推奨
// var y = String(x);    // 非推奨

整数化

var x = 12.3;
var y = x | 0;
var y = ~~x;
var y = x >> 0;
// var y = parseInt(x, 10);
// var y = Math.floor(x);

補足 推奨は速度優先なら| 0または~~、広範囲の数字を取り扱うならparseIntです
注意 Math.floorで負の値を使用すると結果が異なります

true/falseに変換

var x = '';
var y = !!x;
// var y = Boolean(x);

NaNチェック

var x = NaN;
if (x !== x) {
}
// if (isNaN(x) && typeof x === 'number') {
// }
// if (Number.isNaN(x)){  // es2015
// }

注意 isNaNNumber.isNaNは型が数値以外の場合に結果が異なる場合があります

引数の既定値

function foo (x) {
  x = x || {};
// if (typeof x === 'undefined') {
//   x = {};
// }
}

注意 falsyな値が設定されない時のみ使用できます

配列の作成

var arr = 'foo,bar,baz'.split(',');
// var arr = ['foo', 'bar', 'baz'];

文字列を指定回数繰り返す

var x = Array(10 + 1).join('foo');
// var x = '';
// for(var i = 0; i < 10; i++) {
//   x += 'foo';
// }
// var x = "foo".repeat(10); // es6 非推奨

0埋め

var x = 123;
var y = ('00000' + x).slice(-5);
// var y = x;
// var loop = 5 - (x + '').length;
// for(var i = 0; i < loop; i++) {
//   y = '0' + y;
// }

注意 桁あふれの可能性がある場合は、sliceを使うと上位桁が捨てられてしまいます

配列内の存在確認 (嫌)

var x = 'foo';
if (~['foo', 'bar', 'baz'].indexOf(x)) { // 非推奨
}
// if (['foo', 'bar', 'baz'].indexOf(x) !== -1) {  // 推奨
// }
// if (['foo', 'bar', 'baz'].includes(x)) {  // es7 非推奨
// }

一括代入

var x, y;
x = y = 1;
// x = 1;
// y = 1;

代入時に初期化されてないなら同時に行う (嫌)

var obj;
(obj || (obj = {})).prop = 'foo';
// if (!obj) {
//   obj = {};
// }
// obj.prop = 'foo';

値の入れ替え swap

var a = 1;
var b = 2;
(function(a_, b_){a = b_; b = a_;})(a, b); // 推奨
a ^= b; b ^= a; a ^= b; // 非推奨
b = [a, a = b][0];  // 非推奨
// var tmp = a; a = b; b = tmp; // 推奨
// [a, b] = [b, a]; // es6 非推奨

参考 JavaScript, XOR による swap
参考 Javascriptでswap

nullとundefinedのみ分岐 (嫌)

var foo;
if (foo == null) {
}
// if (foo === null || typeof foo === 'undefined') {
// }

補足 jsHintの既定値ではアラートの出る構文です

配列の判定

var foo = [1, 2, 3];
var isArr = Object.prototype.toString.call(foo) === '[object Array]'; // 非推奨
// var isArr = Array.isArray(foo); // 推奨

参考 JavaScript で配列を判定する正しいやり方

配列を空にする

var foo = [1, 2, 3, 4];
var bar = foo;
bar.length = 0;
// bar.splice(0, bar.length);

参考 参照を切らずに配列を空にする

長さ指定で配列を作成

var arr = Array.apply(null, {length:10});
// var arr = [];
// for (var i = 0; i < 10; i++) {
//   arr[i] = undefined;
// }

参考 JavaScriptで配列の生成

配列の複製

var foo = [1, 2, 3];
var bar = foo.concat();
var bar = [].concat(foo);
var bar = Array.prototype.slice.call(foo);
var bar = [].slice.call(foo); // 推奨
// var bar = foo.map(function(x){return x;});  // その1
// var bar = [];    // その2 非推奨
// for(var i = 0;i<foo.length;i++){
//   bar[i] = foo[i];
// }

補足 後のイディオム"可変引数を配列にする"との関係のため、推奨はsliceです。

配列の重複排除

var a = [1,2,3,3,2,2,5];
var b = a.filter(function (x, i, self) {
  return self.indexOf(x) === i;
});
var b = Array.from(new Set(a)); // ES2015

参考 配列の重複をはじく、もしくは重複を取り出す

可変引数を配列にする

function fn () {
  var params = [].slice.call(arguments);
}
fn(1, 2, 3, 4, 5);

注意 先のイディオム"配列の複製"のconcatargumentsに使用しても意図したものになりません。

ヒアドキュメント

var heredoc = (function () {/*
<div class="title">
    <h1>
        <a href="${url}">${title}</a>
    </h1>
</div>
*/}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1];

参考 Javascriptでヒアドキュメント
注意 jsをminifyすると動作しなくなります。その時はヒアドキュメントは諦めましょう。

ヒアドキュメント風 (嫌)

var heredoc = '\
<div class="title">\n\
    <h1>\n\
        <a href="${url}">${title}</a>\n\
    </h1>\n\
</div>\
';

補足 safari,chrome,firefox,operaでは動作しました。ieは調査できず不明。

new 忘れ対策

function Klass() {
  if (!(this instanceof Klass)) {
    return new Klass();
  }
  this.prop1 = 'foo';
}

switch(true) (嫌)

var i = 5, j = 15; 
switch(true) {
case i < 10 && j < 10:
  break;
case i < 10:
  break;
case j < 10:
  break;
default:
  break;
}
// if (i < 10 && j < 10) {
// } else if (i < 10) {
// } else if (j < 10) {
// } else {
// }

参考 switch(true) イディオム考察
補足 バットノウハウだと感じる人がとても多いイディオムです。

三項演算子の連結 (嫌)

var x = 3;
var y = x === 1 ? 'first'  :
        x === 2 ? 'second' :
        x === 3 ? 'third'  :
        'big number';
// var y;  // その1
// switch(x) {
// case 1:
//   y = 'first';
//   break;
// case 2:
//   y = 'second';
//   break;
// case 3:
//   y = 'third';
//   break;
// default:
//   y = 'big number';
//   break;
// }
// var y;  // その2
// if (x === 1) {
//   y = 'first';
// } else if (x === 2) {
//   y = 'second';
// } else if (x === 3) {
//   y = 'third';
// } else {
//   y = 'big number';
// }

補足 三項演算子の自体が嫌いな人も多いです。

スコープ化

(function() {
  var x = 'foo';
  // xはローカル変数
})();

ifの省略 true編

var flg = true;
flg && fn();
// if (flg) {
//   fn();
// }

ifの省略 false編

var flg = false;
flg || fn();
// if (!flg) {
//   fn();
// }

ifの省略 三項演算子&カンマ演算子編 (嫌)

var flg = false;
var value = flg ? 'foo' : (fn(), 'bar');
// var value;
// if (flg) {
//   value = 'foo';
// } else {
//   fn();
//   value = 'bar';
// }

補足 普通はカンマ演算子を意識して使う事はほとんどありません。

メソッドが存在したら実行

var obj = {
  fn: function() {}
};
obj && obj.fn && obj.fn();
// if(obj) {
//   if(obj.fn) {
//     obj.fn();
//   }
// }

補足 if省略 true編を応用したイディオムです

関数自身に変数

function fn() {
  fn.count = fn.count || 0;
  return fn.count++;
}
// var count = 0;
// function fn() {
//   return count++;
// }

処理が失敗するまで順次処理

function fn1 (x) {
  return x < 10;
}
function fn2 (x) {
  return x < 5;
}
function fn3 (x) {
  return x < 3;
}
var y = 4;
var result = fn1(y) && fn2(y) && fn3(y);

処理が成功するまで順次実行

function fn1 (x) {
  return x === 1;
}
function fn2 (x) {
  return x === 2;
}
function fn3 (x) {
  return x === 3;
}
var y = 2;
var result = fn1(y) || fn2(y) || fn3(y);

forの代わりにwhile

var items = ['foo', 'bar', 'baz'];
var x, i = 0;
while(x = items[i++]) {
}
// for (var i = 0, len = items.length; i < len; i++) { // その1
//   x = items[i];
// }
// items.forEach(function(x){ // その2 推奨
// });

注意 whileではfalsyな値がxに代入された場合に繰り返しを抜けます

whileの代わりにarray.some

var items = [1, 2, 3, 4];
items.some(function (x){
  if (x === 3) {
    return true;
  }
});
// var x, i = 0;
// while(x = items[i++]){
//   if (x === 3) {
//     break;
//   }
// }

参考 ループ途中で抜ける処理は for(in break) ではなく some を使おう♪

処理部のないwhile (嫌)

var items = ['foo', 'bar', 'baz'];
var i = items.length;
while(i && fn(items[--i]), i);
// while(i) {
//   i--;
//   fn(items[i]);
// }

補足 itemsを逆順に処理しています。

オブジェクトのキーで順次処理

var obj = {
  foo: 1,
  bar: 2,
  baz: 3
};
Object.keys(obj).forEach(function(key) {  // 推奨
  var value = obj[key];
});
// for (var key in obj) {
//   if (obj.hasOwnProperty(key)) {
//     var value = obj[key];
//   }
// }

参考 for-inとObject.keysの違いを正しく知る

ネームスペースの確認し作成

if (typeof nsFoo === 'undefined') {
  nsFoo = {};
}
nsFoo.bar = {};

補足 ライブラリを複数ファイルに分けた際に、クライアントサイドで読み込み順序に左右されなくなります

グローバルオブジェクトの取得

var global = (function(){return this})(); // その1
var global = (0, eval)('this');           // その2

補足 その1はstrict modeではundefinedを返します
参考1 globalオブジェクトを取得する
参考2 (0, eval)('this')とは何なのか

本投稿の対象

対象は コアjavascriptのみ としています。
クライアントサイドのオブジェクト(DOMとか)やjQueryなどライブラリに関しては、数が多すぎるので対象外です。
サーバサイドのみのオブジェクト(node.jsのライブラリなど)も対象外です。
また、コアjavascriptのオブジェクトですが、正規表現(RegExp)に関しても書き始めるとキリがないので対象としていません。

おわりに

この投稿は継続して更新する予定です。
JavaScript イディオム集で書かれた内容が素晴らしいので、多くを参照しています。
ただ時間が経っているため、それ以外にもQiitaやいろんなブログに新しいイディオムが分散して溜まってきました。
いちいち見るのが面倒なので、ここにまとめていきます。
(まとめて欲しいものがあった場合は、コメか編集リクエストください)
参考にしたサイトはそれぞれのイディオムの 参考 に記述しますが、私自身すでに忘れてしまったのも多いので思い出したら追加します。
また複数のイディオムに関連している場合は、最後の参考サイトで確認してください。

紹介しておいてなんですが。。。
あまりにもトリッキーすぎて初見では処理がものすごく理解し難いイディオムもあります。
あくまでここで紹介しているイディオムはリーディングに役立てるものです。
けっして「俺って頭いい(ドヤッ」とするためではありません。

ライティングでの使用は慎重になりましょう

ずいぶんと数が増えたので、グルーピングしようかと思うけど。。。わかりにくくなりそうだな

参考サイト