__proto__プロパティの不思議

__proto__プロパティ周りでJavaScript実装によっていろいろ挙動が異なることが分かったのでメモ。

以下のブラウザ(エンジン)で調べた

また、GoogleChromeとNode.jsでの結果は同じだった。

var bind = Function.prototype.bind,
    hasOwn = bind.bind(bind.call)(Object.prototype.hasOwnProperty),
    proto = Object.getPrototypeOf;

Object.prototype の __proto__

Object.prototype の __proto__ プロパティ
CodeFirefoxGoogleChromeOpera
hasOwn(Object.prototype, "__proto__") true false false
"__proto__" in Object.prototype true true true
desc(Object.prototype, "__proto__") [Object object] undefined undefined
Object.prototype.__proto__ null null null

GoogleChrome, Opera の __proto__ は in 演算子では存在することになっているが、どこにあるん?
Firefox も不思議で、デスクリプタとしては({ configurable:false, enumerable:false, value:(void 0), writable:true })なのに、nullが返ってきたり...

普通のオブジェクト

var o = {};
普通のオブジェクト o
CodeFirefoxGoogleChromeOpera
"__proto__" in o true true true
hasOwn(o, "__proto__") false false false
hasOwn(proto(o), "__proto__") true false false

Firefoxだけ少し違う結果だが、これはObject.prototypeの特性がそのまま出ている。

defineProperty による __proto__ 定義(普通のオブジェクト編)

o__proto__プロパティを定義してみよう。

Object.defineProperty(o, "__proto__", { value: { foo: "FOO" } });
oにdefinePropertyで__proto__定義
CodeFirefoxGoogleChromeOpera
hasOwn(o, "__proto__") true true true
o.__proto__ === Object.prototype false true false
proto(o) === Object.prototype true true true
o.__proto__.foo "FOO" undefined "FOO"

GoogleChromeが変。後述するけど、defineProperty__proto__を定義しようとしても無視されるっぽい。

プロトタイプを継承しないオブジェクト

ES5 からプロトタイプを持たない素のオブジェクトが作成できる。こちらで試してみよう。

var o2 = Object.create(null);
[[Prototype]]を持たないo2
CodeFirefoxGoogleChromeOpera
"__proto__" in o2 false true true
o2.__proto__ undefined null null
proto(o2) null null null

FirefoxにはObject.prototype__proto__があり、それが継承されないオブジェクトと考えると正しい結果となっているように見える。が、GoogleChroe, Opera では、in演算子では存在し、__proto__プロパティもnullを返す結果となる。

__proto__プロパティの代入
o2.__proto__ = { foo: "FOO" };
[[Prototype]]を持たないo2に__proto__代入
CodeFirefoxGoogleChromeOpera
o2.foo undefined "FOO" "FOO"
hasOwn(o2, "__proto__") true false false
o2.__proto__.foo "FOO" "FOO" "FOO"
proto(o2).foo TypeError: proto(o2) is null "FOO" "FOO"

Firefoxでは__proto__が普通のオブジェクトとして定義され、GoogleChrome, Operaでは[[Prototype]]としてプロトタイプに定義される結果となる。

defineProperty による __proto__ 定義
var o3 = Object.create(null);
Object.defineProperty(o3, "__proto__", { value: { hoge: "HOGE" } });
[[Prototype]]を持たないo3にdefinePropertyで__proto__定義
CodeFirefoxGoogleChromeOpera
"__proto__" in o3 true true true
hasOwn(o3, "__proto__") true true true
o3.__proto__; ({hoge: "HOGE"}) null ({hoge: "HOGE"})

GoogleChromeではdefinePropertyで__proto__はできない(エラーにもならない)。

まとめ(?)

  • FirefoxではObject.prototype__proto__があり、これが[[Prototype]]へのgetter/setterの様な役割を担っている
    • Object.prototypeをプロトタイプチェーンに持たないオブジェクトでは__proto__によるプロトタイプ代入は不可
  • GoogleChrome, Opera では、in演算子が変。[[Prototype]]があろうが無かろうがtrueを返す
  • definePropertyで__proto__をやると、GoogleChromeではブラックホールに呑まれる
    • [[Prototype]]とはならない、__proto__は定義できないようになっていると思われる