JavaScript プロトタイプチェーンの探検
旧題:Chromeの仕様の甘さを見つけた件について
興が乗ってしまったので、JavaScriptのプロトタイプチェーンを遡って、隠蔽されたものも含めて全プロパティを列挙するクラスを書いてしまった。え?院試は?そんなん知らん。
列挙可能プロパティは、内部の列挙可能enumerableフラグが true に設定されているプロパティです。これは、単純な代入や初期化で作成されたプロパティのデフォルトです (Object.defineProperty で追加したプロパティはデフォルトで列挙可能性が false になります)。
もうそれが面白いのなんのって。Promiseとか windowとかfetchとか手当たり次第にプロパティ全列挙してみてる。無限にやってられるわ、これ。
そしたらproperty列挙でこんなエラー。
https://gyazo.com/7a8aa44cc1086b33f9f913ed79df2834
HTMLElementのstyleの全プロパティ(600ぐらいある)を列挙してみたら、Object.getOwnPropertyNamesでの全列挙とObject.getOwnPropertyDescriptorsの全列挙で食い違いがありました、という。まぁそれだけなんだけど。
もちろんScrapboxObjectの全列挙とかも試してます。
https://gyazo.com/6a35697a786bc077b46cf49902afb2fd
まぁこれはあんまり面白くないね。enumerableカラムが falseなのが一応隠しプロパティなのと、ここにはprototypeチェーン全部遡ったやつが乗ってるので、例えばemitとかは使えそうってわかる。まぁすでに誰かが明らかにしてそうw
一方で、これはObjectを表示してるところなんだけど、上よりも明らかに多い。
https://gyazo.com/1ab0764b9edd8913296cf4bc39d5cb7f
これはおかしい、Objectは全てのプロトタイプのはず。
JavaScript のほぼすべてのオブジェクトが Object のインスタンスです。
プロトタイプのプロパティも全列挙してるはずでは??と思ってコードを見返してみたけど、バグはない。でも、この引用のすぐそばに答えがあった。
一般的なオブジェクトは、プロパティを (メソッドを含めて) Object.prototype から継承しています(後略)
Objectじゃない。Object.prototypeから継承しているんだ。これは目から鱗だった。
どのオブジェクトもプロトタイプと呼ばれる、他のオブジェクトへの内部的な繋がりを持っています。そのプロトタイプオブジェクトも自身のプロトタイプを持っており、あるオブジェクトのプロトタイプが null に到達するまでそれが続きます。 null は、定義によれば、プロトタイプを持たず、プロトタイプチェーンの最終リンクとなります。
つまり、一般のObjectはObject.prototypeのインスタンス。Object.prototype.prototypeがnull。ややこしわ。
JSは全てのオブジェクトがprototypeになりうる。だからfetch.prototypeはFunctionだし、Function.prototypeはObject。でもObject.prototypeだけは名前がないw
そしてほぼ全ての親になっているのはObjectではなく Object.prototype。だからObjectよりも他のオブジェクトの方がメソッドが少なかったりする。Objectはむしろ、ユーティリティクラスに近い働きかもしれない。
訂正。
JavaScript の関数は、実際にはすべて Function オブジェクトです。これは、 (function(){}).constructor === Function というコードが true を返すことで確認することができます。
こう書いてあるけれども、実際に継承しているのはwindow["Function"]そのものではなくて、Function.prototype。つまり、MDNの、「XXを継承」と言う表現は、厳密にはXX.prototypeを継承していると言う意味らしい。ややこしいわ。
code:sample.js
Function.isPrototypeOf((function(){}))
false
Object.getPrototypeOf(Function).isPrototypeOf((function(){}))
true
だから、Objectを継承しているからといってentriesとかkeysとかのObjectメソッドを持っているわけじゃない。
基本的に、JavaScriptランタイムのビルトインオブジェクトのプロトタイプチェーンは、ほとんど無名のオブジェクト(nameプロパティが空)からできている。
ちなみにfetchのプロトタイプはFunctionのプロトタイプ。
XMLHttpRequestは対照的に、XMLHttpRequest -> XMLHttpRequestEventTarget -> EventTarget -> Function.prototype と言う面白いプロトタイプチェーンを持っている。
全部遡った後のプロパティはこんな感じ。
https://gyazo.com/b88df3a8256ca35ba3c09d3fd0556e46
EventTargetはFuntionのプロトタイプを継承してるのか〜。確かにonloadとかだから関数だよね。
windowのプロパティからたどれる全てのビルトインObjectの継承関係とかD3.jsで図示してみたいな。