JavaScriptにはnullundefinedと、立場が似たものがあるわけですが、さらにこのundefinedにも二種類あるよってお話です。

「本当に無い」のと「「無い」がある」のと

値がundefinedであるときは、文字通り「未定義」である場合と、「未定義という値」である場合とがあります。

var obj = { a: undefined };

console.log(obj.x);  // => undefined(定義されていないプロパティ)
console.log(obj.a);  // => undefined(未定義という値)

二種類のundefinedの見分け方

in演算子で判別できます。

var obj = { a: undefined };

console.log('x' in obj);  // => false(ないよ)
console.log('a' in obj);  // => true(あるよ)

継承したものの判別は .hasOwnProperty()

このときinはprototype chainをたどります。そのオブジェクト自体で定義されていなくても、継承されていればtrueになります。

これを見分ける場合は .hasOwnProperty()を使います。

// 元のオブジェクト
var obj1 = { prop_defined_at_obj1: undefined };

// obj1を継承したオブジェクトを作成するコンストラクター
function F() {}
F.prototype = obj1;

// obj1を継承したオブジェクト
var obj2 = new F();
obj2.prop_defined_at_obj2 = undefined;  // 独自に値を追加

で、それぞれ確認してみます。

// obj2で定義されたもの
console.log(obj2.prop_defined_at_obj2);  // => undefined
console.log('prop_defined_at_obj2' in obj2);  // => true
console.log(obj2.hasOwnProperty('prop_defined_at_obj2'));  // => true

// obj1で定義されたもの
console.log(obj2.prop_defined_at_obj1);  // => undefined
console.log('prop_defined_at_obj1' in obj2);  // => true
console.log(obj2.hasOwnProperty('prop_defined_at_obj1'));  // => false

// どこでも定義されていないもの
console.log(obj2.prop_not_defined);  // => undefined
console.log('prop_not_defined' in obj2);  // => false
console.log(obj2.hasOwnProperty('prop_not_defined'));  // => false
プロパティ名 得られる値 in判定 .hasOwnProperty()判定
obj2.prop_defined_at_obj2 undefined true true
obj2.prop_defined_at_obj1 undefined true false
obj2.prop_not_defined undefined false false

変数

変数も定義だけして値を代入してないときはundefinedになります。

var a;
console.log(a);  // => undefined

定義自体していない場合は参照エラーになります。

try {
  console.log(variable_not_defined_anywehere);  // => ReferenceError
}
catch (e) {
  console.log(e);
}

巻き上げ

ちなみに定義はスコープ内のどこでも良いです。JSエンジンがパース時に自動的に「巻き上げ」てくれます。

try {
  console.log(variable_defined_at_last);  // => undefined
  console.log(variable_not_defined_anywehere);  // => ReferenceError

  var variable_defined_at_last = 123;
}
catch (e) {
  console.log(e);
}

ただし宣言は巻き上げても値の代入は巻き上げないので、先に参照した場合はundefinedを得る事になります。

つまりこれ↓

console.log(a);
var a = 123;

は、これ↓

var a;
console.log(a);
a = 123;

と同じ。

in.hasOwnProperty()の速度

inにはprototype chainをたどるコストが、.hasOwnProperty()には関数実行のコストがかかります。どっちが早いかな?

inの方が早いらしい。

よくわからないこと

なんでinは演算子なのに .hasOwnProperty()はメソッドなのか?

メソッドの方が演算子よる便利なのは、上書きできる点。(もちろんデメリットでもあるけど。) .toString()みたいに暗黙的に使われてたりすると便利かもしれないけど、どこかで使われる機会があるのかな。

prototype chainをたどる .hasProperty()があってそれと対になっている、というのなら納得できるんだけど、内部的に[[HasProperty]]があるものの、外から扱えるメソッドじゃあないんだよなあ。

参考

これ見て思いつきました。