LoginSignup
1
0

More than 1 year has passed since last update.

JavaScriptのnewを探検する

Posted at

JavaScriptのnewを探検する

new:それは新しいオブジェクトとの出会い。
つまり、クラスからインスタンスを生成する演算子です。

ただ、噂によるとJavaScriptのクラス・オブジェクトの関係は他の言語のオブジェクトとちょっと違うらしい。

ちょっと違うって何が違うんだ。
newよ、お前はその短い3文字の中にどんな意味をはらんでいるんだ?

そんなことを考えていると眠れない夜が続いたので、自分で実験してみることにした。

クラスの作り方の歴史

JavaScriptでクラスを作るとき下記の様に書く。
他はどうか知らないが、私はこう書いている

ES2015以降のクラスの書き方
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はもともと関数からクラスを作っていたらしい。
やってみたら本当にできた。

ES5以前の書き方
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というプロパティがある、ということしか発見していないぞ、順番に試していこう。

インスタンスに__proto__は実在した!
/* クラス定義は省略 */
const croissantCreamTaiyaki = new Taiyaki('クロワッサン', 'クリーム')
console.log('__proto__' in croissantCreamTaiyaki)                    // true
クラスのprototypeとインスタンスの__proto__が辿り着く先は同じな様だ
/* クラス定義は省略 */
const croissantCreamTaiyaki = new Taiyaki('クロワッサン', 'クリーム')
console.log(croissantCreamTaiyaki.__proto__ === Taiyaki.prototype)   // true
インスタンスでなくても、__proto__内のメソッド・プロパティは親オブジェクトから直接呼び出せる
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__に渡せばいいんだ。
と半ばやけくそに次の様なコードを書いてみた。

prototypeしか渡さない
function Foo() {}
console.log({__proto__: Foo.prototype})

こちらがその実行結果だ

Foo!?

我々はその光景に目を疑った、そこにはちゃんとFooのインスタンスが表示されていたのである。

あとは、この空っぽなインスタンスにプロパティを設定すべく、コンストラクターを呼び出せる様にしたらnewを再現することができるぞ!

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と同じっぽいことをすることができました。

foo2.png

表示もちゃんとFooになってますね。

ん、ちょっとまて、コンストラクターでもメソッドが使えないと困ると思って、
勝手に事前にthis__proto__を仕込んでしまったが、本当に本物のnewもそうなっているのだろうか?

最後にそれだけ確認して、この記事を終わろう

普通にnewしてもちゃんとthisに__proto__は入っていた
function Foo() {
    console.log('__proto__' in this) // true
}

new Foo()

よかった、今夜はぐっすり眠れそうだ。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0