JavaScript

引数を渡さなかった際にデフォルト値を使う場合の注意点

More than 3 years have passed since last update.


よくやる variable = argument || default_value; パターン は割と罠

jQuery のソースコードなり、色んな実装で見るこのパターンのことです。

/**

* @param {number} value
* @param {number=} opt_value
* @param {(function():*)=} opt_callback
*/

function doSomething(value, opt_value) {
var option = opt_value || 10; // opt_value が渡されていなかったらデフォルト
/**
* 以下なんらかの処理
*/

}

これは undefined を boolean キャストして false が返る仕様を利用したテクニックですが、実際は割と限定された用途で書けるものだと捉えた方が良いです。


まずいパターン

このようなコンストラクタを作った場合

function Hoge(foo, opt_bar) {

this.foo = foo || 'foo';
this.bar = opt_bar || 10;
}


目的を持って null を渡す場合に無効化される

nullundefined 同様 boolean として扱うと false を返します。

var hoge = new Hoge('hey', null);

hoge.bar; // 10


opt_value が 0, [], {}, false, '' が渡って、それに意味がある場合

同様に boolean として扱うと false になるものもすべて無効化されます。

var hoge = new Hoge('hey', 0);

hoge.bar; // 10


opt_value をバリデーションしないと挙動が想定してない結果になる場合

利用側でそもそも渡すべきではない、という方針もありだけど (というか、そうであれば optional arguments を設計に入れない方が良い)、こういった失敗のパターン

/**

* @param {Array.<number>} scores
* @param {number=} opt_index
* @return {number}
*/

function average(scores, opt_index) {
var l = opt_index || scores.length, // option 引数で配列を絞り込むつもり
sum = 0;
for (i = 0; i < l; i++) {
sum += scores[i]; // l > scores.length であった場合 undefined が加算
}
return sum / l; // l > scores.length であった場合 sum が NaN になってる
}


対策


opt_value === undefined ? default_value : opt_value; パターンにする

関数宣言内では typeof undefined_value == 'undefined' としなくても RefferenceError にはなりません。以下のように三項演算子で書くと、確実に引数が渡されていなかった、あるいは undefined を渡していた時のみデフォルト値を代入します。

function Hoge(foo, opt_bar) {

this.foo = foo || 'foo';
this.bar = opt_bar === undefined ? 10 : opt_bar;
}


supplememt() メソッドみたいなのを作る

バリデーションも込みで効率化したい場合、(というか、↑ はたくさん書くとすごい読みづらいし糞コード化する)


supplememt(default_value, opt_arg, opt_callback)

/**

* @description 関数宣言内で引数をバリデーションしたり
* @param {*} default_value
* @param {(*)=} opt_arg
* @param {(function(*,*):*)=} opt_callback
* @return {*}
*/

function supplement(default_value, opt_arg, opt_callback) {
if (opt_arg === undefined) {
return default_value;
}
if (opt_callback === undefined) {
return opt_arg;
}
return opt_callback(default_value, opt_arg);
}


こう使う

これで 先のサンプルをこう修正します。

/**

* @param {Array.<number>} scores
* @param {number=} opt_index
* @return {number}
*/

function average(scores, opt_index) {
function validate(def, val) {
return Math.max(Math.min(def, val), 0);
}
var l = supplement(score.length, opt_index, validate),
sum = 0;
for (i = 0; i < l; i++) {
sum += scores[i];
}
return sum / l;
}

以上です