タイトル暴論ですみません、ごろどくですどうも。
オレ得JavaScriptメモの6枚目か7枚目か8枚目くらいです。だいたいそれくらい。
今回は前回のprototypeとプロトタイプとプロトタイプチェーンの話に引き続き[[prototype]]と__proto__について。
[[prototype]]プロパ…ティ?
プロトタイプとprototypeプロパティとプロトタイプチェーンと[[prototype]]と__proto__。紛らわしすぎる。最初の3つについては前回のメモで書いた通り。
そして4つ目の[[prototype]]。結論を書いてしまおう。
JavaScriptの実装において[[prototype]]プロパティなどというものは「存在しない」。しないと言ったらしない。しないんだってば。マジだよ。
[[prototype]]は概念的にはプロパティである。そしてその役目は「あるオブジェクトのプロトタイプを知りたいとき、どのオブジェクトを参照すればよいかをメモっておく」ことである。
前回のサンプルコードにこんなのがあったね。
function Abc(){}
Abc.prototype.p = 56;
var xyz = new Abc;
console.log(xyz.p);//56
Abcという関数はここではコンストラクタで、xyzはインスタンスだ。xyzはAbcを元に作られてる。このとき「Abcはxyzのプロトタイプである」という。そう、「プロトタイプ」というのはオブジェクト同士の関係を表す概念だった。
そしてプロトタイプ(例ではAbc)から作られた新しいオブジェクト(例ではxyz)は、自分自身があるプロパティやメソッドを持たないときに、プロトタイプのprototypeプロパティを調べて、目的のプロパティやメソッドがあればそれを返す。prototypeプロパティは代替応答すべきものを格納しておく特殊なプロパティだった。
プロトタイプチェーンというのはこのような関係がいくつも繋がって「プロトタイプのプロトタイプのプロトタイプの…」と祖先を辿っていき、最終的にObjectオブジェクトのprototypeプロパティまで調べに行く(それでもなければundefinedを返す)、一連のつながりの様だ。
ここで考えてほしい。なぜxyzはAbcが自分のプロトタイプであることを知っているのか?なぜ自分の持たないプロパティをAbcのprototypeプロパティへ調べに行くのか?
そのオブジェクト同士の繋がりがどこかに記されていて然るべきだ。「prototypeプロパティを調べる」という行為はルールとして決まっていればそれでいい。そしてそうなっている。単なる決まり事だ。
そのとき「どのオブジェクトの?」という情報を格納しているのが[[prototype]]だ。実際には「どのオブジェクトの?」を直接記しているのではなくて「参照すべきオブジェクトが記憶されているメモリ上の番地」が格納されているのだが。
[[prototype]]は一見プロパティのように見えるが、JavaScriptの実装上では見ることも操作することもできない。あえて疑似コードとして書くとこんな感じになる。
function Abc(){}
Abc.prototype.p = 56;
var xyz = new Abc;
console.log(xyz.[[prototype]];//Abc.prototype(が記憶されている○×○×○番地)
[[prototype]]は概念的な内部プロパティなので上のコードを書いてももちろん動作しないが、これでイメージはなんとなく掴めたはず。
[[prototype]]を具現化する__proto__プロパティ
そこで登場するのが__proto__プロパティだ。上記疑似コードの[[prototype]]を__proto__に置き換える。
function Abc(){}
Abc.prototype.p = 56;
var xyz = new Abc;
console.log(xyz.__proto__);//Object {p: 56}
p=56というプロパティを持つオブジェクトを返した。これはAbc.prototypeにほかならない。__proto__は「[[prototype]]を実装上で参照・操作しうるオブジェクトとして取り扱えるようにしたプロパティ」ということになる。
prototypeプロパティがさらにどのオブジェクトに遡るべきかはもちろんprototype.__proto__を見ればわかる。上のコードに二行足してみよう。
function Abc(){}
Abc.prototype.p = 56;
var xyz = new Abc;
console.log(xyz.__proto__);//Object {p: 56}
console.log(Abc.prototype.__proto__);//Object {}
console.log(Object.prototype.__proto__);//null
xyzのプロトタイプはAbc.prototypeで、Abc.prototypeのプロトタイプは空のオブジェクト、つまりObject.prototypeで、Object.prototypeのプロトタイプはnull。
このようにxyzからObjectオブジェクトまで一連のチェーンで繋がり、チェーンのどのrototypeプロパティにないものはnullにももちろんないから、最終的にundefinedとなる。こうするとプロトタイプチェーンというものの見通しがかなり良くなったのではないだろうか。
注意しなければならないのは__proto__プロパティは過去に非標準の仕様だったということ。ECMAScript5(ECMAScriptはJSのベースとなる仕様だ)までは各実装ごとに独自の仕様として用意されていた。だから環境によっては__proto__が使えなかったりする。
2015年6月に策定されたECMAScript6では標準として採用され、各実装毎の対応状況は
ECMAScript 6 compatibility table
こちらで確認できる。「__proto__in object literals」や「Object.prototype.__proto__」の項目を見ると、デスクトップブラウザやモバイルではそこそこ対応が進んでいるものの、コンパイラ関係ではほぼほぼ全滅だったりする(2016年3月23日現在)。
なのでweb界隈でフロントエンドとしては気にしなくてもいいけど、「まだ当たり前に使えるものとは限らない」というのは頭の片隅に置いておいた方がいいかもしれない。
また、標準/非標準、対応/非対応の問題のほかに
Object.prototype.__proto__ – JavaScript | MDN
__proto__の使用は、論争の的になり、推奨されていません。EcmaScript言語仕様にもともとは含まれていませんでした。しかし、最新のブラウザはとにかくそれを実装することを決定しました。今日、__proto__ プロパティはECMAScript第6版言語仕様で標準化されました。そして、将来にサポートされます。それにもかかわらず、オブジェクトの[[Prototype]]を変化させることは、性能を気にしている場合避けるべき低速の操作です。
ということもあるようなので、以下のように__proto__プロパティを操作して
function Abc(){}
Abc.prototype.p = 56;
var xyz = new Abc;
console.log(xyz.__proto__);//Object {p: 56}
function Lmn(){}
Lmn.prototype.p = 78;
Lmn.prototype.q = 34;
xyz.__proto__ = Lmn.prototype;
console.log(xyz.__proto__);//Object {p: 78, q: 34}
[[prototype]]の振り先変えられたりできるけど速度とトレードオフになるので気を付けよう、ということも。チェーンの繋がりもよく解らなくなるしね。
まとめ
[[prototype]]はチェーンの繋がりを示すために、オブジェクト同士を紐付ける概念的内部プロパティ。
__proto__プロパティはその[[prototype]]を実際に参照したり操作したりするために作られた実装上のプロパティ。だが必ずしも全てのJavaScript実装(環境)で使えるとは限らない。
[[prototype]]と__proto__はプロトタイプチェーンの仕組を理解するために知っておいて損はないけど、実際に使えるかどうかとか、また使えるとして使うべきかどうかは別問題ってこと。
そんな感じで前回・今回でプロトタイプチェーン関係はかなり整理できた気がする。クラスベースの言語のように、ひな形(クラス)オブジェクトと実態(インスタンス)オブジェクトを強い拘束を持ちながら明瞭に区別するような言語とは全然違う、というのもなんとなく見えた。プロトタイプベース言語とクラスベース言語を比較してどっちが良い悪いなんて議論はナンセンスだし、一方に他方の概念を中途半端に持ち込むのは禁忌とすら思える。
さてひと段落したところで次に何書くかは決めてない。そもそも書くかどうかも決めてないけどね。気が向いたらスコープとかスコープチェーンとかクロージャのこと調べて書くかも。たぶん書かない。んじゃまたー。
コメント