JavaScriptのundefined と nullの違いを調べた

そもそもの前提として書いてる自分はJSを丁度入門中で 書籍とか見ながら勉強している所です。 という事で、TypeScriptの章で 3ページ目ぐらいで プリミティブ型として nullundefined が明確に区別されていました。

PHPとかやってると null とかは馴染みあるんだけど undefined とかが出てきてよくわからん! ってなったので調べてみてみます。( undefined ならエラーになりますし )

どういう意味があって、どうやって使い分けるかみたいなのをキャッチアップできればいいな。。って感じですかね。

公式見解

typescript-jp.gitbook.io

  • TypeScriptチームは、nullを使いません
  • 誰もがundefinedだけを使うべきだと考えています
  • だそうです

まとめていく

演算子の振る舞い(比較とか)

JavaScript(と、TypeScript)は、nullとundefinedという2つのボトム型(bottom type)があります。これらは異なる意味を持っています。

とあるので、明示的に 異なる意味を持っている という状態ではあるみたいです。

ただし、比較としては、以下のように null == undefined は True と評価されます。

console.log(undefined == undefined); // true
console.log(null == undefined); // true

また string | null | undefined の Union Types を持っている変数 arg について は以下のようにコーディングすると アーリーリターンとか出来ますね。(もっと書き方ありそう)

if (arg == null) {
  // null or undefined
  console.log("null or undefinedとなる");
} else {
  console.log("args が stringである事が確定する");
}

また、 加算演算子 ( + ) とかも注意が必要です。

console.log(1 + null) // 1
console.log(1 + undefined) // NaN 

上記のような挙動をします。

これは JavaScriptの仕様として、 toNumber(null) は 0 / toNumber(undefined) は NaN とするという取り決めがあるから、という事のようでした。

qiita.com

型のキャスト

デフォルトでは、 nullとundefined は TypeScriptのすべての型に代入できるみたいです。 という事で、上記のコードはどちらも正常に動作します。

let foo: number = 123;
foo = null; // Okay
foo = undefined; // Okay

これを防ぐ場合は、 strict を true で動かしたり、 strictNullChecks を true にしたりする必要があります。

typescript-jp.gitbook.io

上記オプション下では、 以下のように Union Types を指定する事で代入が可能です。

let foo: number = 123;
foo = null; // Error

let hoge: number | null = 123;
foo = null; // OK

使い分けの意識

公式見解としては undefined を使うべきとあるし、 1 + null1 になる以上、undefined を使った方が NaN になってくれるし、おかしい時に落ちてくれそう、という点なのは何も否定は無かったです。

だけど、全然腹落ちしていない所として。。。。以下の例があって、この辺全然わからないですね。

const a = { id: 1, name: undefined };

console.log(a['name']); // undefined

ってなる。

aというobjectについて name プロパティが 未定義(undefined) である事が定義されている みたいな意味に取れるけど、それって・・・何・・? みたいな。 この場合だと null を使うべきなのでは? みたいなのがよく分かってないです。

変数が初期化されていない事をundefinedと表すけど、変数の初期化にundefinedを指定できる みたいな・・その辺が腹落ちしてなくて、もう少し概念を理解するのに時間が必要だな、という気持ちがあります。

という事で こういうのを引き続きキャッチアップしていきたいと思います・・!!

追記

JavaScriptのundefinedは必要ですか?nullと区別する必要はありますか? - Quora

その現実を踏まえて、どう扱っていけばよいかについてですが、
関数返り値としてエラーの意味でnullやundefinedを返す悪習を断つ。
{valid:boolean,value?:返り値 }のようにエラーを区別できるオブジェクトを返すあるいは例外で返す。
非同期コールバックはpromisifyする。nullは.catch、非nullは.thenに流れるようにする。 関数の返り値の型を動的にしない(typescriptでいう、null許容、undefined許容、つまりnullやundefinedとのユニオンにしない)
変数はconstを多用し、let使うときも宣言と初期値指定を同時に行う
ここでもtypescriptで言えばundefined許容型、null許容型をさける。
typescriptの場合引数チェックは静的型で守る。
オプショナル引数は明示したもののみ許す。
デフォルト値を設定することでundefined非許容にできる
オプションオブジェクトの柔軟性は型でグルーピング分けしてユニオンで提供するなどし、実行時に先送りをなるべくしない。
値がないことを明示するためにnullを代入したりするのではなく(初期化後の代入や更新は、破壊的操作なのでそれ自体好ましくない)、値をそもそも設定しないことで(結果的にundefined)空を表す。
ローカル変数初期値にnullを与え、forやforEachのループで何かを探してローカル変数に代入する、というやり方を避け、mapやsome,findで探す。
要するにTypeScriptを使えば、曖昧にしてたことを曖昧にできなくなります。「似たような二種類の値がある」という現象を見るのではなく、個々の変数が生存期間を通じてどんな値を持ちうるかを、静的型で最低限の集合として吟味して絞り込み、保証できるようになるからです。

結果として、nullやundefinedを逃げに使う必要がなくなるので、区別の意識すらなくなっていくでしょう。3という値と"abc"という値を区別することに、普通迷ったり苦労する意識はないと思いますが、それと同じです。nullやundefinedが渡ってくることがないことが静的型で保証されれば、迷う必要も区別する必要もありはしません

具体的には、truthyや!=nullで判定することも含めて、まず「"undefined"」がソース上にはあまり現れなくなり、また関数型プログラミングを援用すれば、nullを表記上も、値として使うことも激減します。

つい、TypeScriptでの対策話も織り込んでしまいましたが、そこでどう解決されるかを理解してからJavaScriptに戻るのは有効だと思います。なぜならJSでも解決策を検討しやすくなるでしょうし、目をつぶるにせよ、JSの特性、もしくは限界として納得して目をつぶれるようになると思うからです。

ちなみに、TypScriptチームの開発コーディングルールは、nullの使用禁止[1]です。つまり、そこで不要とされてるのundefinedではなくnullのほうです。