JavaScriptのnewを探検する
new
:それは新しいオブジェクトとの出会い。
つまり、クラスからインスタンスを生成する演算子です。
ただ、噂によるとJavaScriptのクラス・オブジェクトの関係は他の言語のオブジェクトとちょっと違うらしい。
ちょっと違うって何が違うんだ。
new
よ、お前はその短い3文字の中にどんな意味をはらんでいるんだ?
そんなことを考えていると眠れない夜が続いたので、自分で実験してみることにした。
クラスの作り方の歴史
JavaScriptでクラスを作るとき下記の様に書く。
他はどうか知らないが、私はこう書いている
class Taiyaki {
constructor (material, ingredient) {
this.material = material
this.ingredient = ingredient
}
getName () {
return this.material + this.ingredient + 'たいやき'
}
}
const croissantCreamTaiyaki = new Taiyaki('クロワッサン', 'クリーム')
console.log(croissantCreamTaiyaki.name) // 'クロワッサンクリームたいやき'
ただ、噂によるとJavaScriptはもともと関数からクラスを作っていたらしい。
やってみたら本当にできた。
function Taiyaki (material, ingredient) {
this.material = material
this.ingredient = ingredient
}
Taiyaki.prototype.getName = function () {
return this.material + this.ingredient + 'たいやき'
}
const croissantCreamTaiyaki = new Taiyaki('クロワッサン', 'クリーム')
console.log(croissantCreamTaiyaki.getName()) // 'クロワッサンクリームたいやき'
上下を見比べて、私の様な凡人にでも理解できたことは
- 関数に
new
をつけて呼び出すとclass
で書いた時のconstructor
として振る舞う - 関数の
prototype
というプロパティににメソッドを設定していたら、インスタンスから呼び出せる
ということ。
関数のprototype
というプロパティににメソッドを設定していたら、インスタンスから呼び出せる・・・?
なんでや。
われわれは謎を解明するためアマゾンの奥地へ向かった。
プロトタイプチェーン 〜JavaScript内を流れるアマゾン川〜
さらに調査をすすめたところ、prototypeに関する驚きの話を耳にした。
人々はprototypeに関して、口を揃えて次の様にいうのである。
- JavaScriptではクラスが
ptorotype
、インスタンスが__proto__
というプロパティを持っている -
__proto__
はprototype
を参照渡ししたもの -
__proto__
プロパティ内にあるメソッドやプロパティは親オブジェクトから直接呼び出せる
今のところ、クラスにptorotype
というプロパティがある、ということしか発見していないぞ、順番に試していこう。
/* クラス定義は省略 */
const croissantCreamTaiyaki = new Taiyaki('クロワッサン', 'クリーム')
console.log('__proto__' in croissantCreamTaiyaki) // true
/* クラス定義は省略 */
const croissantCreamTaiyaki = new Taiyaki('クロワッサン', 'クリーム')
console.log(croissantCreamTaiyaki.__proto__ === Taiyaki.prototype) // true
const obj = {
__proto__: {
method() {
console.log('Method was called.')
},
text: 'Some text.',
},
}
obj.method() // 'Method was called.'
console.log(obj.text) // 'Some text.'
この事実から次のことが見えてきた。
- クラスは
prototype
というメソッドを持ったオブジェクトである - インスタンスは
prototype
の参照を__proto__
プロパティに持った、新しく生成されたオブジェクトである -
__proto__
プロパティは親オブジェクトから中身を呼び出せる
なるほど、、これらの性質を組み合わせてクラスとインスタンスの関係を再現しているわけか
トリッキーなのは__proto__
の中身が親オブジェクトから呼び出せるというくらいなので、そんなに理解は難しくなさそうだ。
ただ、ここでいくつかの疑問が湧いてくる
-
__proto__
にprototype
の参照を渡しているだけなのであれば、new
を用いずに再現できるのでは? - もし、
new
を用いずに再現できるのであればブラウザは何を持ってインタンスと判断している? - ていうか、継承どうやるん?
今回はnew
を探検するのが目的なので上の2つの疑問を解決すべく、引き続き探検を続けていこうと思う。
(継承に関してはまたいずれ・・・)
ついにnewの内部に潜入
とりあえず、関数オブジェクトのprototype
の参照を__proto__
に渡せばいいんだ。
と半ばやけくそに次の様なコードを書いてみた。
function Foo() {}
console.log({__proto__: Foo.prototype})
こちらがその実行結果だ
我々はその光景に目を疑った、そこにはちゃんとFooのインスタンスが表示されていたのである。
あとは、この空っぽなインスタンスにプロパティを設定すべく、コンストラクターを呼び出せる様にしたらnew
を再現することができるぞ!
function newInstance (ClassObject, ...args) {
const instance = {__proto__: ClassObject.prototype}
ClassObject.call(instance, ...args)
return instance
}
function Foo(message) {
this.message = message
}
Foo.prototype.win = function () {
console.log(this.message)
}
const foo = newInstance(Foo, 'Haha! We overcame "new".')
foo.win() // 'Haha! We overcame "new".'
ってなわけで、無事new
っぽい関数を作ってnew
と同じっぽいことをすることができました。
表示もちゃんとFoo
になってますね。
ん、ちょっとまて、コンストラクターでもメソッドが使えないと困ると思って、
勝手に事前にthis
に__proto__
を仕込んでしまったが、本当に本物のnew
もそうなっているのだろうか?
最後にそれだけ確認して、この記事を終わろう
function Foo() {
console.log('__proto__' in this) // true
}
new Foo()
よかった、今夜はぐっすり眠れそうだ。