# undefinedについて

# はじめに

undefinedはグローバルに定義されたプリミティブなオブジェクトである。
ちなみに、ビルトインのグローバルオブジェクトは以下の4つである。

  • Infinity
  • NaN
  • null
  • undefined

undefinedは値であるが、nullはリテラル表現をもつ。undefinedの挙動について以下簡単にまとめる。

# ECMAScript 5 以前

undefinedはグローバル変数で、以下のように書き換えることができてしまっていた。

console.log(undefined);  // => undefined
var undefined = "hoge";
console.log(undefined);  //=> "hoge"

# ECMAScript 5.1 以降

ES5.1 以降ではグローバルなundefinedは書き換え不可となった。変数ではなくグローバルな値として扱うことができる。

console.log(undefined);  // => undefined
var undefined = "hoge";
console.log(undefined);  //=> undefined

モダンブラウザ (JavaScript 1.8.5 / Firefox 4 以降) での undefined は、ECMAScript 5 仕様により、設定不可、書込不可のプロパティとなります。そうでない場合でも、上書きは避けてください。

undefined - JavaScript | MDN Annotated ES5 | 15.1.1.3 undefined

直接undefinedを代入するか、値を代入せず宣言のみにした場合に変数はundefinedを返す。

const x = undefined;  // undefined
let y;                // undefined

ただし、ローカルスコープ内では依然としてundefinedには値を代入できる。

const isUndefined = arg => {
  const undefined = 'hoge';
  return arg === undefined;
};
console.log(isUndefined(undefined));  // => false
console.log(isUndefined('hoge'));     // => true

undefinedは予約語でなはいため、ローカル変数としてundefinedを定義できてしまう。
変数を参照する際はローカルスコープから辿っていくため、任意の値を代入したローカル変数undefinedを採用してしまう。

対して、void演算子は任意の引数をとって常にグローバルのundefinedを返すため、安全性が保たれる。

console.log(void 'hogehoge');  // => undefined

const isUndefined = arg => {
  const undefined = 'hoge';
  return arg === void 0;
};
console.log(isUndefined(undefined));  // => true

もしくは、ローカルスコープ内でも先頭でconst undefined = undefinedと宣言しておけばスコープ内でundefinedの書き換えを防ぐことができるが、全スコープないでこれを行うのは現実的ではない。

もっとも、ローカルスコープ内でundefinedに値を代入するなんてことは避けるべきである。

# undefnedの評価

# 等価演算子===

変数に値が割り当てられているかを調べる。

let x;
console.log(x === undefined);  // => true
console.log(x === void 0);  // => true

変数がまだ宣言されていないときはReferenceErrorを投げる。

console.log(x === undefined);  // Uncaught ReferenceError: x is not difined

ReferenceErrorを発生させたくない場合には使用できない。引数の確認などでは有効。

# 比較演算子==

変数に値が割り当てられていないか、nullが代入されているかを調べる。

const x = null;
console.log(x == undefined);  // => true
console.log(x == null);       // => true

const y = undefined;
console.log(y == undefined);  // => true
console.log(y == null);       // => true
console.log(x == undefined);  // ReferenceError: x is not difined
console.log(x == null);       // ReferenceError: x is not difined

[変数] == nullとしておけば、ローカルスコープ内でundefinedに値が代入されようとも常にグローバルのundefinedを参照するため、nullundefinedを検出する際は有効である。

nullチェック以外の場合では比較演算子は使わない。

# typeof演算子

変数に値が割り当てられていない場合だけでなく、変数が宣言されていない場合も文字列"undefined"を返す。

console.log(typeof x === 'undefined');  // => true
let x;
console.log(typeof x === 'undefined');  // => true

変数が宣言されていな場合でもReferenceErrorを投げずに変数が未定義かチェックすることができ、文脈に依存せず使用できる。

# 2種類のundefined

undefinedには2つのタイプがある。

  • 変数宣言されているが値が代入されていない場合の、値としてのundefined
  • オブジェクトにプロパティが存在しない場合の、不存在のundefined

以下は同じundefinedを返すが、内部の意味的には異なる。

const obj = {
  hoge: undefined
};
console.log(obj.hoge);  // => undefined
console.log(obj.fuga);  // => undefined

これらを区別するにはObject#hasOwnProperty()メソッド、またはin演算子を使用する。

const obj = {
  hoge: undefined
};
console.log(obj.hasOwnProperty('hoge'));  // => true
console.log(obj.hasOwnProperty('fuga'));  // => false
console.log('hoge' in obj);               // => true
console.log('fuga' in obj);               // => false

# 配列のempty

配列をコンストラクタで生成すると要素がemptyの配列が得られる。

new Array(3);  // [empty × 3]

これは値としてのundefinedではなく、配列オブジェクトでインデックスに対応したプロパティが存在しないことを表している。

その他にも、以下のような場合にemptyが発生する

  • 値を指定せずカンマ,を連続で入れる
  • Array#length以上のインデックスに値を代入する
  • delete演算子で要素を削除する
const list = [,, 1, 2, 3];
list[10] = 4;
delete list [3];
console.log(list);  // => [empty × 2, 1, empty, 3, empty × 5, 4]

配列オブジェクトではundefinedが代入されている場合とインデックスに対応したプロパティが存在しない場合とを区別している。
しかし、インデックスからプロパティを呼び出すときはいずれの場合もundefinedが返される。

const list = new Array(3);
list[0] = undefined;
console.log(list);     // => [undefined, empty × 2]
console.log(list[0]);  // => undefined
console.log(list[1]);  // => undefined
console.log(list[2]);  // => undefined

undefinedemptyではコールバック関数を引数にとる配列のメソッドを使用したときの挙動が異なる。

const list = new Array(3);
list[0] = undefined;
list.forEach((ele, index) => {
  console.log({ index, ele });
});
// => {index: 0, ele: undefined}
// index が 1, 2 の場合はスキップされる

配列の要素が空かどうかは、オブジェクトにプロパティが存在しない場合と同じように、インデックスをキーとするプロパティが存在するかを調べればよい。

const list = [,];
console.log(list.hasOwnProperty(1));  // => false
list[1] = 'hoge';
console.log(list.hasOwnProperty(1));  // => true

長さを指定した配列を生成して値を埋めたい場合はforで対処するか、Array#fill()を使う。

new Array(3).fill(1);  // [1, 1, 1]

# まとめ

評価は以下のように行うと確実である。

  • undefinedであるかを評価
    • 変数が確実に宣言されている (引数など) - [変数] === void 0
    • 変数が宣言さているか不確定 - typeof [変数] === 'undefined'
  • undefinedまたはnullであるかを評価 - [変数] == null
  • オブジェクトがプロパティを持つかどうかを評価 - Object#hasOwnProperty([変数])

細かい仕様については歴史的な経緯もあったりしてややこしい部分があるので注意しないといけない。

# 参考

undefined - JavaScript | MDN
空じゃないけど空の配列の話。(配列とかおれおれAdvent Calendar2018 – 22日目)

Last Updated: 2019-11-27 1:19