JavaScript | コンストラクタでインスタンス生成するときの動きを深堀りしてみた【初心者向け】

※この記事の内容はES6以降のクラス構文は対象としていません。ご注意ください。

どうもかわうそです!

今日は『JavaScript | コンストラクタでインスタンス生成するときの動きを深堀りしてみた【初心者向け】』というテーマで、記事を書いていきます。

結論をざっくりまとめると

『コンストラクタとはnewされることを前提に書かれた、ただの関数』
『newすることで、クラスからオブジェクトが生まれてthisに格納され、インスタンス変数に代入される』

みたいなすごく難しいところを、画像+コードで、出来る限り分かりやすく説明しております。


JavaScriptの学習で必ず出てくる『コンストラクタやインスタンス』。

オブジェクト指向やクラス 』 と一緒に、突如として現れます。

正直、使いどころもピンとこないし分かりやすい説明もなく、もやもやしてしまいますよね。

かわうそはというと

コンストラクタとかインスタンスとか知らなくても書けるし、とりあえず分かった気になって進んじゃおう・・・』

と、すっ飛ばしました。(おい笑)

というわけで、『過去の自分のおさぼり を回収するべく、インスタンスを作成する流れを深堀ってみました。

かわうその『そういうことか~! 』 が、みなさんにも届くように頑張ります!

ここが理解できれば、グッと中級者に近づけるはずです!

thisとnewを行ったり来たりして、頑張っていきましょう!

インスタンス作成時の 『 this』が必要な理由が分からない

インスタンス作成時、 なぜ thisがいるのかピンとこなかったです。

インスタンス生成時、thisはインスタンス自身 になる』 というのも、もやもやしていました。

ということでまず、この『thisがインスタンス作成時にどういう動きをしているのか 』 、順を追って調べてみました!

thisがインスタンス自身になる流れを調べてみた

まず、クラスからインスタンスを作成する一般的な例文を書いてみます。

こうなるのは分かるけど、なぜかは説明できない状態です。

インスタンス生成時のthisはインスタンス自身になる

みたいです。

では、この『thisが一体何なのか 』 を実際に調べてみましょう。

というわけで、humanインスタンスを作成した際のthisは

Human {name: "かわうそ"}

だと分かりました。

とりあえず分かったこと①
thisにはクラスから作られたオブジェクト{}が格納される

インスタンス生成時のthisにはオブジェクトが格納される

この Human {name: “かわうそ”} のHuman、
chromeのコンソール画面で確認すると、出てくると思います。
とりあえずスルーしてもらってokかなと思います。
どのクラスから生まれたインスタンスなのか名前が付くみたいです!(継承元のオブジェクトを参照しているっぽい)
console.log(human.constructor.name); で確認もできますよ。

続いて、インスタンス変数であるhumanを調べてみましょう。

human = Human {name: "かわうそ"}
this = Human {name: "かわうそ"}

thisと同じオブジェクトが返ってきました!

human = this ということですね。

これで、thisがインスタンス自身になるというのも、とりあえずは納得。

★とりあえず分かったこと②
インスタンスであるhuman = this になる。

インスタンス変数にはthisのオブジェクトが代入される

human = {name: "かわうそ"} なんだから、
human.nameは”かわうそ”を返すのですね~!!

human.nameでプロパティを参照できるのは分かりましたね。

あれ?でも、なんで human = thisになったんだ??

そこでかわうその疑問。

なぜreturnをしていないのに、humanにthisが格納されたの??

普通ならundefinedになるはずですよね。

これを少し調べてみたいと思います。

なぜreturnをしていないのに、humanにthisが格納されたのか調べてみる

ということで実験。Humanから文字列"aaa"を返すように、指示してみました。

ぐぬぬぬぬ。。。

Human {name: "かわうそ"}が返ってきました。

return "aaa" が無視されていますね。

これは絶対、newの仕業な気がする!!!ということで、newを取ってみました。

つまり、Humanファンクションをただただ実行してみました。

よし!aaaが返ってきたぞ!

というわけで、newすると強制的にreturn thisが発動しているみたいです。

★とりあえず分かったこと③
newすると強制的にreturn this発動

newはインスタンス生成時にreturn thisを強制発動する

では、newじゃないときのthisってどうなってるんだ??と疑問になりました。

ということで調べてみましょう。

newじゃないときのthisってどうなってるのか調べてみる

なんとなく想像がつくかもですが、newなしの時のthisを調べてみましょう。

というわけで、windowオブジェクトが返ってきました。

関数実行時、thisはwindowオブジェクトを参照します。


thisは 動きが複雑です。

・メソッド呼び出し時、定義されているオブジェクトを
・インスタンス生成時はインスタンス自身を
・メソッドではない、(プロパティではない)通常の関数実行時はグローバルオブジェクト(window)を
・イベントでは、イベント発生元を

それぞれ参照します。覚えておきましょう。

thisはこのサイトが分かりやすいかもです。

ここまでの話が少し難しかった人は、こちらの記事も参考になると思います。


newがなければ(インスタンス作成しなければ = 関数を通常実行すれば)、thisはwindowオブジェクトを参照します。

つまり、this = window  this.name = window.name だということ。

というわけで、以下の実行結果になります。

windowオブジェクトに、nameプロパティが追加されたのが確認できました。

★とりあえず分かったこと④
newしないとthisはwindowオブジェクトを参照し、windowオブジェクトにプロパティを追加する。

newしないと、thisはwindowオブジェクトを参照する

では次に、本題。thisがない場合はどうなるのでしょうか。

調べてみましょう。

thisがない場合はどうなってるのか調べてみる

ようやく本題です。

ここまでthisの動きを寄り道しながら調べてきました。

なんとなく、もうthisがないといけないような気がしていますよね。

thisがないとどうなるの?

やってみましょう。

newした場合の、thisとnameを調べてみました。

this = Human{}
human.name = undefined

が返ってきましたね。

ちなみに、この時

window.name = "かわうそ"

が返ってきます。nameプロパティはwindowオブジェクトに追加されてるみたい。

window.alert()alert(); で実行できるように、
windowオブジェクトのプロパティやメソッドは、window.を省略できます。

つまり、thisがなければ、Humanはwindowオブジェクトへプロパティやメソッドを追加してしまうので、thisは必須だというわけですね!

これで

this = Human{}
human.name = undefined

この原因も分かりましたね。

thisのオブジェクトにnameプロパティが追加されなかったので、空のオブジェクトになっています。

オブジェクト自体は存在するけれど、 中が空っぽなので、nameプロパティの参照結果はエラーではなく、undefinedだったのですね!

★とりあえず分かったこと
thisがないと、windowオブジェクトにプロパティやメソッドが設定されてしまう。
newでインスタンスを作成して初めて、this=インスタンスにプロパティを追加できる

と、ここまでthisを中心に話を展開してきました。

しかし、どうやらnew についてもしっかり理解をしないと、
クラスをnewしてインスタンス!の流れは掴めそうにありません。

そこで、ここからはさらにnewについて深掘りをしてみたいと思います!

さらにnewについて深掘りをする

さらにnewにいくんかい!と突っ込みがありそうなボリュームです。

しかし、かわうその探求心は止められません!

というわけで、ここまでnew関連について分かったことをおさらいしておきましょう。


★とりあえず分かったこと③
newすると強制的にreturn this発動

newはインスタンス生成時にreturn thisを強制発動する

★とりあえず分かったこと④
newしないとthisはwindowオブジェクトを参照し、windowオブジェクトにプロパティを追加する。

newしないと、thisはwindowオブジェクトを参照する

★とりあえず分かったこと
thisがないと、windowオブジェクトにプロパティやメソッドが設定されてしまう。
newでインスタンスを作成して初めて、this=インスタンスにプロパティを追加できる

この画像には alt 属性が指定されておらず、ファイル名は 5.png です

とりあえず、『newしないと期待しているインスタンス作成ができない! 』って感じですね!

このnewについて 、もう少し掘り下げてみましょう。

そもそもnewとは

ほんっっっっとにびっくりするぐらい、本やブログによっていろいろな表現がありました(本質的には同じであると信じたいですが・・・)

かわうそ的に一番しっくり来た、newの説明はこちら。

実は、「 オブジェクト の インスタンス を 返却 し なさい! と コンス トラクタ に 命令 する

五十嵐肇; 多良間斎. 何となくJavaScriptを書いていた人が一歩先に進むための本 Kindle 版.

え!?
newってコンストラクタじゃなかと!?!?

とかわうそは驚きました。

しかし、

全てのオブジェクトには、そのオブジェクトと同じ名前のコンストラクタメソッドが用意されている。

https://teratail.com/questions/58028

MDNの説明にも、むずかしいですが同義の内容が書かれています。

Object.prototype.constructor - JavaScript | MDN
constructor プロパティは、インスタンスオブジェクトを生成した Object のコンストラクター関数への参照を返します。なお、このプロパティの値は関数そのものへの参照であり、関数名を含んだ文字列ではありません。

つまり、

全オブジェクトは生成時、コンストラクタメソッドを持つ。しかも、オブジェクトと同じ名前で。

これはきっと正しい。

となると冒頭で 、 一番しっくり来たと書いた、newとは

実は、「 オブジェクト の インスタンス を 返却 し なさい! と コンス トラクタ に 命令 する

五十嵐肇; 多良間斎. 何となくJavaScriptを書いていた人が一歩先に進むための本 Kindle 版.

この内容も、そういうことか、となんとなく分かってもらえるはず。

つまり、俗にいう『コンストラクタ 』というのは

newのことではなく、
あらゆるオブジェクトに用意されているコンストラクタメソッドに、
newを使ってインスタンスを返す命令をする
『newとオブジェクトのこと

であると思われます。

もっとざっくり、しかも正しかろう認識は

コンストラクタとはnewされることを意図して書かれた関数のこと = クラス的な構文

って感じがしっくりくるのではないでしょうか。

newdでインスタンス生成される関数がコンストラクタ = クラス

★とりあえず分かったこと
まとめると、newはオブジェクトに、『インスタンスを返しなさい! と命令し、インスタンスを受け取っているらしい

newに関しては、ここでは、そういうものだと理解しておくほうがよさそうですね。


※ちなみにこれは完全に余談ですが、newには関数実行機能もついています。

本来であれば、Human('かわうそ');、引数を入れなくても実行命令としては
Human();と、( );が必要なはずです。

しかし、( );がなくても、console.log('かわうそ')が実行されています。

つまり、newはnewする関数を強制実行する機能があるのです。

(まぁ、強制的にreturn this させてインスタンスを返させるぐらいなので、当然っちゃ当然な気もしますね~!)

こういう探りもおもしろいですね!


余談でした。

次に気になっているのは、なぜ、
newすれば 『 インスタンスにthisが代入されるのか

ってところです。

ここをもう少し深掘りしてみました。

なぜnewするとオブジェクトが返ってくるのか

この記事の一番最初で、thisの中身を調べたときのことを覚えていますでしょうか。


とりあえず分かったこと①
thisにはクラスから作られたオブジェクト{}が格納される

インスタンス生成時のthisにはオブジェクトが格納される

もともと、クラスHumanは『関数オブジェクト 』 だったはずです。
ばりばり『function(){} !』 言うてますからね!

そこから作成したインスタンスが『オブジェクトであること 』、誤解がないように言えば、『Objectオブジェクト』 であることに違和感を覚えたんです。

違和感というか、『なんでそうなんねん! 』という苛立ち、が適切な表現かもしれない。。。

調べていくと、どうやら

newしたときに空のオブジェクトを生成し、それがthisとなる

みたいです。

とりあえず分かったこと⑦
newすると空のオブジェクト{}が生成され、thisとなる

newは空オブジェクトを作成し、thisにする

こちらの記事がすごく分かりやすかったので、参考にさせてもらいました!

おお、MDNでも再先頭に書いてある。。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/new

それでは調べてみましょう。

という一連の流れが、すごく分かりやすいですね。

this = {} で、this.name でプロパティが追加できる流れが分かりにくい人は

これと全く同じことだと思ってください。

となるとこのシーン。
thisがなくなったらどうなる?を試したとき(覚えているかな?)

this = Human{}
human.name = undefined

今なら、this = { } であることもより理解できると思います。

newによって作成されていたのですね!

反対に、newを取っ払ったこのシーンも。

newをしていない通常の関数実行なので、thisはwindowオブジェクトを参照しているのでしたね。

そしてnewされていないので、humanはオブジェクト{}でもない のですよね。

よって、human.name は、参照できるはずのない場所へ参照をしているので、エラーを起こすはず。

さらに深掘りをして、this=オブジェクト{ }を調べてみたいと思います。

と、var food はなかったことになっています。

それもそのはず。だって humannewにより

returnを受け取りhuman = this = {}になっていて、this = {}の空オブジェクトに追加できるプロパティ記述は、var food = "apple" なんて『変数宣言 』 ではありませんからね。

終わりに

ここまでお付き合いありがとうございました。

丁寧すぎるぐらいに解説をはさんだつもりではありますが、分かりにくいところも多々あると思います!

不明点やもっと解説が必要なところはコメントしてもらえると、大変ありがたいです!

ありがとうございました。

フォローする