※この記事の内容はES6以降のクラス構文は対象としていません。ご注意ください。
どうもかわうそです!
今日は『JavaScript | コンストラクタでインスタンス生成するときの動きを深堀りしてみた【初心者向け】』というテーマで、記事を書いていきます。
結論をざっくりまとめると
『コンストラクタとはnewされることを前提に書かれた、ただの関数』
『newすることで、クラスからオブジェクトが生まれてthisに格納され、インスタンス変数に代入される』
みたいなすごく難しいところを、画像+コードで、出来る限り分かりやすく説明しております。
JavaScriptの学習で必ず出てくる『コンストラクタやインスタンス』。
『オブジェクト指向やクラス 』 と一緒に、突如として現れます。
正直、使いどころもピンとこないし分かりやすい説明もなく、もやもやしてしまいますよね。
かわうそはというと
『コンストラクタとかインスタンスとか知らなくても書けるし、とりあえず分かった気になって進んじゃおう・・・』
と、すっ飛ばしました。(おい笑)
というわけで、『過去の自分のおさぼり 』 を回収するべく、インスタンスを作成する流れを深堀ってみました。
かわうその『そういうことか~! 』 が、みなさんにも届くように頑張ります!
ここが理解できれば、グッと中級者に近づけるはずです!
thisとnewを行ったり来たりして、頑張っていきましょう!
インスタンス作成時の 『 this』が必要な理由が分からない
インスタンス作成時、 なぜ this
がいるのかピンとこなかったです。
『インスタンス生成時、thisはインスタンス自身 になる』 というのも、もやもやしていました。
ということでまず、この『thisがインスタンス作成時にどういう動きをしているのか 』 、順を追って調べてみました!
thisがインスタンス自身になる流れを調べてみた
まず、クラスからインスタンスを作成する一般的な例文を書いてみます。
1 2 3 4 5 6 7 |
//クラス的なオブジェクト var Human = function(name){ this.name = name; } //インスタンス生成 var human = new Human('かわうそ'); console.log(human.name); // かわうそ |
こうなるのは分かるけど、なぜかは説明できない状態です。
インスタンス生成時のthisはインスタンス自身になる
みたいです。
では、この『thisが一体何なのか 』 を実際に調べてみましょう。
1 2 3 4 5 6 7 |
var Human = function(name){ this.name = name; //thisをコンソールに出力 console.log(this); // Human {name: "かわうそ"} } //インスタンス生成 var human = new Human('かわうそ'); |
というわけで、humanインスタンスを作成した際のthisは
Human {name: "かわうそ"}
だと分かりました。
★ とりあえず分かったこと①
thisにはクラスから作られたオブジェクト{}が格納される
この Human {name: “かわうそ”} のHuman、
chromeのコンソール画面で確認すると、出てくると思います。
とりあえずスルーしてもらってokかなと思います。
どのクラスから生まれたインスタンスなのか名前が付くみたいです!(継承元のオブジェクトを参照しているっぽい)
console.log(human.constructor.name); で確認もできますよ。
続いて、インスタンス変数であるhuman
を調べてみましょう。
1 2 3 4 5 6 7 |
//クラス的なオブジェクト var Human = function(name){ this.name = name; } //インスタンス生成 var human = new Human('かわうそ'); console.log(human); // Human {name: "かわうそ"} |
human = Human {name: "かわうそ"}
this = Human {name: "かわうそ"}
thisと同じオブジェクトが返ってきました!
human = this ということですね。
これで、thisがインスタンス自身になるというのも、とりあえずは納得。
★とりあえず分かったこと②
インスタンスであるhuman = this になる。
human = {name: "かわうそ"}
なんだから、human.name
は”かわうそ”を返すのですね~!!
human.nameでプロパティを参照できるのは分かりましたね。
あれ?でも、なんで human = thisになったんだ??
そこでかわうその疑問。
なぜreturnをしていないのに、humanにthisが格納されたの??
普通ならundefinedになるはずですよね。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//returnしている function sample(){ var a = "aaa"; return a; } //returnしていない function sample2(){ var a = "aaa"; } var get = sample(); var get2 = sample2(); console.log(get) //aaa console.log(get2) //undefined |
これを少し調べてみたいと思います。
なぜreturnをしていないのに、humanにthisが格納されたのか調べてみる
ということで実験。Humanから文字列"aaa"
を返すように、指示してみました。
1 2 3 4 5 6 7 8 |
//クラス的なオブジェクト var Human = function(name){ this.name = name; return "aaa"; } //インスタンス生成 var human = new Human('かわうそ'); console.log(human); // Human {name: "かわうそ"} |
ぐぬぬぬぬ。。。
Human {name: "かわうそ"}
が返ってきました。
return "aaa"
が無視されていますね。
これは絶対、new
の仕業な気がする!!!ということで、newを取ってみました。
つまり、Humanファンクションをただただ実行してみました。
1 2 3 4 5 6 7 8 |
//クラス的なオブジェクト var Human = function(name){ this.name = name; return "aaa"; } //newなしで関数実行 var human = Human('かわうそ'); console.log(human); // aaa; |
よし!aaaが返ってきたぞ!
というわけで、newすると強制的にreturn this
が発動しているみたいです。
★とりあえず分かったこと③
newすると強制的にreturn this発動
では、newじゃないときのthisってどうなってるんだ??と疑問になりました。
ということで調べてみましょう。
newじゃないときのthisってどうなってるのか調べてみる
なんとなく想像がつくかもですが、newなしの時のthisを調べてみましょう。
1 2 3 4 5 6 7 |
//クラス的なオブジェクト var Human = function(name){ this.name = name; console.log(this); //window } //newなしで関数実行 var human = Human('かわうそ'); |
というわけで、windowオブジェクト
が返ってきました。
関数実行時、thisはwindowオブジェクトを参照します。
thisは 動きが複雑です。
・メソッド呼び出し時、定義されているオブジェクトを
・インスタンス生成時はインスタンス自身を
・メソッドではない、(プロパティではない)通常の関数実行時はグローバルオブジェクト(window)を
・イベントでは、イベント発生元を
それぞれ参照します。覚えておきましょう。
ここまでの話が少し難しかった人は、こちらの記事も参考になると思います。
newがなければ(インスタンス作成しなければ = 関数を通常実行すれば)、thisはwindowオブジェクトを参照します。
つまり、this = window this.name = window.name
だということ。
というわけで、以下の実行結果になります。
1 2 |
console.log(human.name) //エラー console.log(window.name) //かわうそ |
windowオブジェクトに、nameプロパティが追加されたのが確認できました。
★とりあえず分かったこと④
newしないとthisはwindowオブジェクトを参照し、windowオブジェクトにプロパティを追加する。
では次に、本題。thisがない場合はどうなるのでしょうか。
調べてみましょう。
thisがない場合はどうなってるのか調べてみる
ようやく本題です。
ここまでthisの動きを寄り道しながら調べてきました。
なんとなく、もうthisがないといけないような気がしていますよね。
thisがないとどうなるの?
やってみましょう。
1 2 3 4 5 6 7 |
//クラス的なオブジェクト var Human = function(name){ name = name; console.log(this); // Human{}; } var human = new Human('かわうそ'); console.log(human.name); //undefined |
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しないとthisはwindowオブジェクトを参照し、windowオブジェクトにプロパティを追加する。
★とりあえず分かったこと⑤
thisがないと、windowオブジェクトにプロパティやメソッドが設定されてしまう。
newでインスタンスを作成して初めて、this=インスタンスにプロパティを追加できる
とりあえず、『newしないと期待しているインスタンス作成ができない! 』って感じですね!
このnew
について 、もう少し掘り下げてみましょう。
そもそもnewとは
ほんっっっっとにびっくりするぐらい、本やブログによっていろいろな表現がありました(本質的には同じであると信じたいですが・・・)
かわうそ的に一番しっくり来た、new
の説明はこちら。
実は、「 オブジェクト の インスタンス を 返却 し なさい! と コンス トラクタ に 命令 する
五十嵐肇; 多良間斎. 何となくJavaScriptを書いていた人が一歩先に進むための本 Kindle 版.
え!?
newってコンストラクタじゃなかと!?!?
とかわうそは驚きました。
しかし、
全てのオブジェクトには、そのオブジェクトと同じ名前のコンストラクタメソッドが用意されている。
https://teratail.com/questions/58028
MDNの説明にも、むずかしいですが同義の内容が書かれています。
つまり、
全オブジェクトは生成時、コンストラクタメソッドを持つ。しかも、オブジェクトと同じ名前で。
これはきっと正しい。
となると冒頭で 、 一番しっくり来たと書いた、new
とは
実は、「 オブジェクト の インスタンス を 返却 し なさい! と コンス トラクタ に 命令 する
五十嵐肇; 多良間斎. 何となくJavaScriptを書いていた人が一歩先に進むための本 Kindle 版.
この内容も、そういうことか、となんとなく分かってもらえるはず。
つまり、俗にいう『コンストラクタ 』というのは
newのことではなく、
あらゆるオブジェクトに用意されているコンストラクタメソッドに、
newを使ってインスタンスを返す命令をする
『newとオブジェクトのこと 』
であると思われます。
もっとざっくり、しかも正しかろう認識は
コンストラクタとはnewされることを意図して書かれた関数のこと = クラス的な構文
って感じがしっくりくるのではないでしょうか。
★とりあえず分かったこと⑥
まとめると、newはオブジェクトに、『インスタンスを返しなさい! 』 と命令し、インスタンスを受け取っているらしい
new
に関しては、ここでは、そういうものだと理解しておくほうがよさそうですね。
※ちなみにこれは完全に余談ですが、newには関数実行機能もついています。
1 2 3 4 5 6 7 |
var Human = function(name){ this.name = name; //かわうそを出力 console.log('かわうそ'); //かわうそ } //()を取っ払った var human = new Human; |
本来であれば、Human('かわうそ');
、引数を入れなくても実行命令としてはHuman();
と、( );
が必要なはずです。
しかし、( );
がなくても、console.log('かわうそ')
が実行されています。
つまり、new
はnewする関数を強制実行する機能があるのです。
(まぁ、強制的にreturn this させてインスタンスを返させるぐらいなので、当然っちゃ当然な気もしますね~!)
こういう探りもおもしろいですね!
余談でした。
次に気になっているのは、なぜ、new
すれば 『 インスタンスにthisが代入されるのか』
ってところです。
ここをもう少し深掘りしてみました。
なぜnewするとオブジェクトが返ってくるのか
この記事の一番最初で、thisの中身を調べたときのことを覚えていますでしょうか。
1 2 3 4 5 6 7 8 |
var Human = function(name){ this.name = name; //thisをコンソールに出力 console.log(this); } //インスタンス生成 var human = new Human('かわうそ'); // Human {name: "かわうそ"} |
★ とりあえず分かったこと①
thisにはクラスから作られたオブジェクト{}が格納される
もともと、クラスHumanは『関数オブジェクト 』 だったはずです。
ばりばり『function(){} !』 言うてますからね!
1 2 3 |
var Human = function(name){ this.name = name; } |
そこから作成したインスタンスが『オブジェクトであること 』、誤解がないように言えば、『Objectオブジェクト』 であることに違和感を覚えたんです。
違和感というか、『なんでそうなんねん! 』という苛立ち、が適切な表現かもしれない。。。
調べていくと、どうやら
newしたときに空のオブジェクトを生成し、それがthisとなる
みたいです。
★ とりあえず分かったこと⑦
newすると空のオブジェクト{}が生成され、thisとなる
こちらの記事がすごく分かりやすかったので、参考にさせてもらいました!
おお、MDNでも再先頭に書いてある。。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/new
それでは調べてみましょう。
1 2 3 4 5 6 7 8 |
var Human = function(name){ // this = {}; ここでthis = {} と、空オブジェクトを作成、thisに代入している // this = {} に対して、nameプロパティを追加している this.name = name; // return this 最後に強制的にruturn this発動 } //インスタンス生成 var human = new Human('かわうそ'); |
という一連の流れが、すごく分かりやすいですね。
this = {} で、this.name でプロパティが追加できる流れが分かりにくい人は
1 2 3 |
var a = {}; a.name = "かわうそ" console.log(a.name); //"かわうそ" |
これと全く同じことだと思ってください。
となるとこのシーン。
thisがなくなったらどうなる?を試したとき(覚えているかな?)
1 2 3 4 5 6 |
//クラス的なオブジェクト var Human = function(name){ name = name; console.log(this); // Human{}; } var human = new Human('かわうそ'); |
this = Human{}
human.name = undefined
今なら、this = { }
であることもより理解できると思います。
new
によって作成されていたのですね!
反対に、newを取っ払ったこのシーンも。
1 2 3 4 5 6 7 |
//クラス的なオブジェクト var Human = function(name){ this.name = name; console.log(this); // window } var human = Human('かわうそ'); console.log(human.name); //エラー |
new
をしていない通常の関数実行なので、thisはwindowオブジェクトを参照しているのでしたね。
そしてnew
されていないので、humanはオブジェクト{}でもない
のですよね。
よって、human.name
は、参照できるはずのない場所へ参照をしているので、エラーを起こすはず。
さらに深掘りをして、this=オブジェクト{ }
を調べてみたいと思います。
1 2 3 4 5 6 7 8 |
//クラス的なオブジェクト var Human = function(name, age){ this.name = name; var food = "apple"; this.age = age; } var human =new Human('かわうそ', 20); console.log(human) //Human {name: "かわうそ", age: 20} |
と、var food
はなかったことになっています。
それもそのはず。だって human
はnew
により
return
を受け取りhuman = this = {}
になっていて、this = {}
の空オブジェクトに追加できるプロパティ記述は、var food = "apple"
なんて『変数宣言 』 ではありませんからね。
終わりに
ここまでお付き合いありがとうございました。
丁寧すぎるぐらいに解説をはさんだつもりではありますが、分かりにくいところも多々あると思います!
不明点やもっと解説が必要なところはコメントしてもらえると、大変ありがたいです!
ありがとうございました。