LoginSignup
1
1

More than 3 years have passed since last update.

【JS学習その⑧】コンストラクター関数 ~prototype・new演算子・instanceof~

Last updated at Posted at 2020-09-26

JS学習シリーズの目的

このシリーズは、私ジャックが学んだJavaScriptのメカニズムについてアウトプットも兼ねて、
皆さんと知識や理解を共有するためのものです。
(理解に間違いがあればご指摘いただけると幸いです)

コンストラクター関数とは

新しくオブジェクトを作成するための雛形となる関数

コンストラクター関数は、一般的な関数とは異なり、専用のnew演算子を使ってオブジェクトを生成します。

main.js
function A() {
    this.prop = 0;
}

const obj = new A();

上記のコードのように、
new演算子で作成したオブジェクトをインスタンスと言い、new演算子でインスタンスを作成することをインスタンス化と言います。
※コンストラクター関数では、慣例として関数名の先頭を大文字で書きます。

prototypeとは

オブジェクトに存在する特別なプロパティー
コンストラクター関数と合わせて使用

main.js
function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.hello = function() {
    console.log('hello ' + this.name);
}

const bob = new Person('Bob', 18);
const tom = new Person('Tom', 33);
const sun = new Person('Sun', 20);

bob.hello(); /*hello Bob*/
tom.hello(); /*hello Tom*/

console.log(bob);

上記のコードでは、
prototypeプロパティにhelloメソッドを追加して、それをnew演算子でインスタンス化したオブジェクトから呼び出しています。
よって、実行結果はそれぞれコメントした内容となります。

ここで、console.log(bob)の結果を見てみると、
image.png
上記の画像のように、インスタンスの中に存在する__proto__の中に、先ほどprototypeに追加した
helloメソッドが格納されています。

このことから、インスタンス化した際にはprototypeの参照が__proto__にコピーされるということが分かります。

↑は、非常に重要なので理解しておきましょう!

なぜprototypeを使うのか

前述したように、コンストラクター関数でメソッドを定義する際は、prototypeを使って定義します。
ただ、prototypeを使わなくても直接コンストラクター関数に定義することはできます。
では、なぜprototypeを使って定義するのでしょうか?
それは、「メモリの効率化」のためです。
下記のコードを見てみましょう。

main.js
function Person1(name, age) {
    this.name = name;
    this.age = age;
    this.hello = function() {
        console.log('hello ' + this.name);
    }
}

const bob = new Person1('Bob', 18);
bob.hello(); /*hello Bob*/

function Person2(name, age) {
    this.name = name;
    this.age = age;
}

Person2.prototype.hello = function() {
    console.log('hello ' + this.name);
}

const tom = new Person2('Tom', 33);
tom.hello(); /*hello Tom*/

上記のコードでは、
Person1ではコンストラクター関数に直接メソッドを定義して、Person2ではprototypeにメソッドを定義しています。
どちらも、インスタンス化してメソッドを呼び出した際の実行結果は同じです。
しかし、この2つの違いは、

  • Person1のインスタンスでは、メソッドそのものをメモリ空間にコピーして作成している
  • Person2のインスタンスでは、prototypeに定義したメソッドの参照を__proto__にコピーしている

つまり、Person1の書き方だと、インスタンスを生成する度にhelloメソッドを追加する必要があるので、余分なメモリを消費することになります。
一方、'Person2'の書き方だと、インスタンスを生成した際にprototypeの参照をコピーするため、オブジェクトに格納されているメソッドの参照先は全て一致するので、余分なメモリを消費せずに効率的にプログラムを動かすことができます。

そのため、このprototypeというオブジェクトは、JavaScriptの仕組みを支える重要な技術となります。

new演算子

まず、new演算子とは
コンストラクター関数からインスタンスを作成するために使用する演算子

これまでもnew演算子は使ってきましたが、new演算子でコンストラクター関数からインスタンスを作成する際、
コンストラクター関数の戻り値によって動きが変わってきます。

  • コンストラクター関数の戻り値がオブジェクトの場合は、コンストラクターが返すreturnのオブジェクトを新しいインスタンスオブジェクトとして呼び出し元に返す
  • コンストラクター関数の戻り値がオブジェクト以外、もしくは戻り値のreturnが定義されていない場合は、コンストラクターのprototypeの参照をインスタンスの__proto__にコピーして、コンストラクターで使用している'this'を呼び出し元に返却する(※インスタンスを'this'の参照先としてコンストラクター関数を実行)
main.js
function F1(a, b) {
    this.a = a;
    this.b = b;
    return {};
}

const instance1 = new F1(1, 2);
console.log(instance1); /*{}*/

function F2(a, b) {
    this.a = a;
    this.b = b;
    return 1;
}

const instance2 = new F2(1, 2);
console.log(instance2); /*{a: 1, b: 2}*/

上記のコードでは、
F1では、戻り値がオブジェクトなので、new演算子でインスタンス化した時、returnのオブジェクトをインスタンスオブジェクトとして返すので、結果は(今回の場合){}(空のオブジェクト)となります。

F2では、戻り値がオブジェクト以外のプリミティブ値なので、'new'演算子でインスタンス化した時、(今回はprototypeでメソッドを定義していませんが)'prototype'の参照を__proto__にコピーして、'this'の参照するオブジェクトはインスタンス(今回の場合はinstance2)になります。

instanceofとは

どのコンストラクターから生成されたオブジェクトかを確認する

main.js
function F(a, b) {
    this.a = a;
    this.b = b;
}

F.prototype.c = function() {}

const instance = new F(1,2);

console.log(instance instanceof F); /*true*/
console.log(instance.__proto__ === F.prototype) /*true*/

上記のコードでは、
console.log(instance instanceof Object)で、instanceFのインスタンスなので、trueが返ってきます。
ちなみに、console.log(instance.__proto__ === F.prototype)で、instance.__proto__F.prototypeが同じ参照先のオブジェクトなのでこれもtrueが返ってきます。

instanceofの使用例

instanceofプロトタイプチェーンをさかのぼって検証を行うので、例えば次のように関数に渡す引数が配列オブジェクトかで条件分岐して関数を実行することもできます
(※プロトタイプチェーン・・・プロトタイプの多重形成プロトタイプチェーンと言う)

main.js
function fn(arg) {
    if(arg instanceof Array) {
        arg.push('value');
    } else if (arg instanceof Object) {
        arg['key'] = 'value';
    }
    console.log(arg)
}

fn([]) /*["value"]*/

まとめ

いかがでしたでしょうか。
コンストラクター関数は、ES6から導入されたクラス構文の基礎となる部分なので、しっかり理解しておきましょう!

1
1
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
1