JavaScriptでプリミティブなデータ型を議論するとき、ほとんどの人はStringからNumber、Booleanまでの基本を知っています。これらのプリミティブ型は非常に単純で、その動作は常識に合致しています。しかし、この記事では、ユニークなプリミティブデータ型であるNullとUndefinedに焦点を当て、何がそれらを似ていながらもっともらしいものにしているのかを説明します。
Nullと未定義の理解
JavaScriptでは、nullはリテラルであり、認識できないオブジェクトの値を示すために使用される言語のキーワードです。つまり、「値がない」という意味で使われます。似ていますが、undefined は実際には存在しない値を表します。どちらも完全に不変で、プロパティもメソッドも持たず、プロパティに値を代入することもできません。実際、プロパティにアクセスしたり定義しようとすると、型エラーが発生します。名前が示す通り、これらは完全に無効な値です。
no値で表されるブール値は偽であり、if文のような条件付きコンテキストでは偽として評価されます。これらの2つの値を他の偽の値と比較するには等号演算子を使用します:
null == 0; // false
undefined == ""; // false
null == false; // false
undefined == false; // false
null == undefined; // true
このような類似点があるにもかかわらず、nullとundefinedは等価ではありません。それぞれ固有の型の固有のメンバであり、undefined は Undefined 型、null は Null 型です。等号演算子を使ってこの2つの値を比較するには、以下のように型と値が等しい必要があります:
undefined === null; // false
これは重要な違いであり、さまざまな目的と理由があります。この2つの値を区別するために、undefinedは予期しない値の不在を表し、nullは予期する値の不在を表すと考えることができます。
未定義の生成
未定義の値に対するコードを生成する方法はたくさんあります。通常は、存在しない値にアクセスしようとしたときに遭遇します。この場合、JavaScriptのような動的で型付けの弱い言語では、エラーを発生させる代わりにデフォルトで未定義値を返します。
初期値を指定せずに宣言された変数は、デフォルト値がundefinedになります:
var foo; // デフォルト値はundefinedである
存在しないオブジェクト・プロパティや配列項目にアクセスしようとすると、未定義の値を返します:
var array = [1, 2, 3];
var foo = array.foo; // foo プロパティが存在せず、未定義を返す
var item = array[5]; // インデックス5にアイテムがない配列はundefinedを返す
関数の return 文が省略された場合、undefined が返されます:
var value = (function(){})(); // undefinedを返す
関数呼び出し時に指定されなかった値は、未定義のパラメータ値となります:
(function(undefined){
// パラメータが未定義である
})();
void演算子は未定義の値を返すこともできます。Underscoreのようなライブラリは、この演算子を防御的な型チェッカーとして使用しています:
function isUndefined(obj){
return obj === void 0;
}
***、undefined は未定義の値に初期化された定義済みグローバル変数です:
'undefined' in window; // true
ECMAScript 5では、この変数は読み取り専用です。
Nullの使用例
nullのユースケースは、undefinedとは異なり、nullの方が有用であると考えられているためです。typeof演算子がnull値に作用したときに "object "を返すのは、まさにこのためです。typeofの振る舞いはバグとして認識されており、修正が提案されていますが、後方互換性の目的のために、これは変更されていません。
このため、JavaScript環境では値をnullに設定することはありません。MDN ドキュメントにあるように
APIでは、nullはしばしば、期待できるけれども関連性のないオブジェクトが取得される場所です。
これは DOM に適用されます。DOM は言語に依存せず、ECMAScript の仕様の範囲外です。外部 API なので、存在しない要素を取得しようとすると、未定義ではなく null 値が返されます。
一般的に、変数やプロパティに不変値を代入したり、関数に渡したり、関数から null を返したりする必要がある場合、ほとんどの場合 null が *** の選択です。要するに、JavaScript は undefined を使い、プログラマは null を使うべきなのです。
NULLのもう1つの有効な使用例は、参照が不要になったときに変数を明示的に無効と指定することです。NULL値を代入することで、参照は効果的にクリアされ、オブジェクトが他のコードから参照されていないと仮定して、メモリが確実に回収されるようにゴミ回収が指定されます。
もっと深く
nullやundefinedがブラックホールのようになるのは、その振る舞いだけではありません。これらは通常、他のネイティブオブジェクトや組み込みオブジェクトと同じ連想特性を持っているようには見えません。
ES5では、Object.prototype.toStringメソッドが事実上の型チェックの標準となり、nullとundefinedの一貫性が証明されています:
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(undefined); // [object Undefined]
しかし、Object.prototype.toStringメソッドは、実際にはnullまたはundefinedの内部[[Class]]プロパティを取得するパブリックコンストラクタではありません。ドキュメントによると、呼び出される過程で以下のステップが発生します:
- 値が未定義の場合、"[object Undefined]"を返します。
- この値が NULL の場合、"[object Null]" を返します。
- this "値をパラメータとして渡しながらToObjectを呼び出した結果をOとします。
- Oの内部プロパティ[[Class]]の値をclassとします。
- 結果は、"[object", class, "]"の3つの文字列と、結果の文字列値を連結して返されます。
このメソッドは、null や未定義を検出した場合、単純な文字列の戻り値を返します。ほとんどのメソッドは null や undefined 値が検出された場合に単純な catch と return を含むため、これは仕様全体に共通です。実際、ネイティブ・オブジェクトに関連する内部プロパティが含まれていることを示すものはありません。まるでオブジェクトではないかのようです。もし明示的なスキームがJavaScriptのネイティブ環境内に実際に存在したらどうなるでしょうか?もしかしたら、そのような実装に詳しい人がいるかもしれません。
はんけつをくだす
これらのネイティブオブジェクトがどんなに珍しいものであっても、null と undefined の違い、そして JavaScript の言語基盤におけるそれらの全く異なる役割を理解することは重要です。アプリケーションにおいて画期的な進歩をもたらすことはないかもしれませんが、一般的には開発やデバッグにおいて長期的に有益であることが証明されているだけです。