よくやる 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 を渡す場合に無効化される
null
も undefined
同様 boolean
として扱うと false
を返します。
var hoge = new Hoge('hey', null);
hoge.bar; // 10
opt_value が 0, false が渡って、それに意味がある場合
同様に boolean
として扱うと false
になるものもすべて無効化されます。
ありがちなのは、明示的に 0
を渡して無効化されてるケアレスミス
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;
}
supplement() メソッドみたいなのを作る
バリデーションも込みで効率化したい場合、(というか、↑ はたくさん書くとすごい読みづらいし糞コード化する)
supplement(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;
}
以上です