Overview
過去にバグになってしまったものを忘れないよう書き留めておくシリーズです。
今回の題材は関数パラメータのデフォルト引数です。
const print = ({name, value = 1}) => console.log({name, value});
print({name: "value is null", value: null});
print({name: "value is undefined"});
Target reader
- この結果がわからない方
Prerequisite
- JavaScriptを一通り理解している
Body
答え合わせ
正解はこうなる。
> Object { name: "value is null", value: null }
> Object { name: "value is undefined", value: 1 }
どうだろう?value: null
にnullが来ることを予想できただろうか?
私はできなかった
valueにnullは存在しない前提だったので、nullレコード大量発生に泣いた
ちなみにオブジェクトじゃなくて、分割したらどうだろう?
const print2 = (name, value = 1) => console.log({name, value});
print2("value is null", null);
print2("value is undefined");
答えはこうだ。
> Object { name: "value is null", value: null }
> Object { name: "value is undefined", value: 1 }
オブジェクトかどうかは関係なく、単純にデフォルト値はundefinedに作用してnullには作用しないということだ。
MDNにこの定義はあるのか見たら、undefinedに作用するとはあった。
関数のデフォルト引数 は、関数に値が渡されない場合や undefined が渡される場合に、デフォルト値で初期化される形式上の引数を指定できます。
違和感を解消する
MDN見る限り、デフォルト値はundefinedにしか作用しない。理解した。
だけど、よく考えると今まで下記のように使い慣れたもののはず。
const print = (v) => console.log(v);
print(null|| undefined || true); // true
print(!null); // true
print(!undefined); // true
この原因を究明するためMDNを漁った。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Logical_Operators
true に変換できる値は、いわゆる truthy です。false に変換できる値は、いわゆる falsy です。
false と見ることができる式の例は、null、0、空文字列 ("")、あるいは、undefined と評価されるものです。
…まとめると、論理演算子は非常に感覚に沿ったものだが、デフォルトパラメータはそれとは別物ということだ
Conclusion
言語の理解が浅いとこんなことをやってしまう例として紹介しました。
本来であればテストコードで防御したかったのですが、開発優先だとなかなかテストコードを追加する時間を作れず…
デフォルトパラメータが仕事するのはundefinedの時だけ(パラメータなしを含む)
Appendices
undefinedの使いどころ
nullとundefinedの使い分けは明確なものはないようですね。
TypeScriptではnullは使わないといっていますが、これはContributorのため物で一般的な規約ではないといっています。
Use undefined. Do not use null.
他の言語を習っているとnullを使うかと思いますが、undefinedを使うと効果的なユースケースが一つあるので紹介しておきます。
そのユースケースとはJSONにシリアライズするときです。
変換の際に undefined、 関数 (Function)、シンボル (Symbol) は有効な JSON 値ではありません。変換中にそのような値に遭遇した場合は、 (オブジェクトの中で発見された場合は) 省略されたり、 (配列の中で見つかった場合は) null に変換されたりします。
WebAPIでリクエストを返す際、値が無効なフィールド名を除外したい場合があります。
その無効な値にundefinedを設定することで、条件分岐や三項演算子等なしに除外することが可能です。
上記の引用にある通り、配列でのundefinedは除外されませんので、その場合はfilter(v => v)
等で除去しましょう。
(filter(v => v)はfalsyとして定義された値 (false, 0, "", null, undefined, NaN)が該当するため、除外してはいけないものがないことに注意すること)
References
分割代入
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
デフォルト引数
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Default_parameters
undefined
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/undefined
null
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/null
等価性の比較と同一性
https://developer.mozilla.org/ja/docs/Web/JavaScript/Equality_comparisons_and_sameness
論理演算子
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Logical_Operators
JSON.stringify()
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify