LoginSignup
181
180

More than 3 years have passed since last update.

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

Last updated at Posted at 2012-12-26

よくやる 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 になるものもすべて無効化されます。
ありがちなのは、明示的に 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;
}

以上です

181
180
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
181
180