Javascript

【JavaScript】prototypeプロパティについて理解をまとめる

こんにちは、ともです。

この記事ではJavaScriptのprototypeプロパティについて理解をまとめておこうと思います。

JavaScriptではオブジェクトにメソッドを定義する場合にprototypeプロパティを利用して定義することが可能です。

prototypeプロパティを利用したメソッド・プロパティの定義方法について書きます。

参考にしたものは下記です。

prototypeプロパティとは

prototypeプロパティはMDNによると次のように書かれています。

JavaScript におけるすべてのオブジェクトは Object に由来します。すべてのオブジェクトは Object.prototype からメソッドとプロパティを継承していますが、それらは上書きされている可能性があります。例えば、他のコンストラクタのプロトタイプは constructorプロパティを上書きしており、それぞれの持つ toString メソッドを提供しています。Object のプロトタイプオブジェクトへの変更は、その変更に関連するプロパティとメソッドがプロトタイプチェーンによってさらに上書きされている場合を除いて、すべてのオブジェクトに影響します。

Objectに属するデータはprototypeというプロパティを持っていることが分かります。

このprototypeプロパティを利用することで、オブジェクトにメソッドやプロパティを定義することが可能となります。

prototypeの利用方法

Object.prototype.プロパティ名 = 値
Object.prototype.メソッド名 = function() {}

上記のような記述方法でprototyoeオブジェクトを利用しObjectにメソッドとプロパティを追加することが可能です。

またオブジェクトリテラリを利用して定義する場合は次のように定義します。

Member.prototype = {
  getName: function () {
    return 'getName function'
  },
  setName: function () {
    return 'setName function'
  },
}

prototypeを利用してみる

var Member = function (firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

Member.prototype.getName = function () {
  return this.firstName + this.lastName;
}

var mem1 = new Member('田中', '太郎');
var mem2 = new Member('山田', '二郎');

console.log(mem1.getName());
console.log(mem2.getName());

このようにMemberクラスを定義し、prototypeプロパティを利用しgetNameメソッドを定義しました。

これによりMemberクラスから生成されたmem1・mem2インスタンスはgetName()を利用することができるようになります。

とてもシンプルですが、なぜprototypeを利用してメソッドを定義する理由はどこにあるのでしょうか?

var Member = function (firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.getName = function () {
    return this.firstName + this.lastName;
  }
}

var mem1 = new Member('田中', '太郎');
var mem2 = new Member('山田', '二郎');

console.log(mem1.getName());
console.log(mem2.getName());

このようにクラス内に定義するだけではダメなのでしょうか。

prototypeを利用する利点

prototypeを利用する利点は下の2点です。

  1. メモリ使用量の節減
  2. 動的にメソッドを追加し、インスタンスへ共有

メモリ使用量の節減

JavaScriptはプロトタイプベースのオブジェクト指向です。

そのため、『インスタンス化=値のコピー』となります。

つまりクラスに記述したメソッドの記述量の多い場合、それだけメモリを消費することになります。

メソッドに関して使い回すことを目的としているので、値のコピーではなく参照で十分です。

プロトタイプで定義した場合を図解してみました。

  1. prototypeでmethodAを追加
  2. 生成されたインスタンスでmethodAを実行するが自身のプロパティに存在しない
  3. クラスを暗黙的に参照しmehtodAを発見し実行

このステップを踏みます。

まずは自身のプロパティを確認し存在しなければクラスのプロパティを確認するためクラスに直接、関数を定義した場合に比べ関数分のメモリ消費が抑えられます。

動的にメソッドを追加し、インスタンスへ共有

var Member = function (firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

var mem1 = new Member('田中', '太郎');
var mem2 = new Member('山田', '二郎');

mem2.getName = function () {//追加!
  return this.firstName + this.lastName;
}

console.log(mem1.getName());//getName is not a function
console.log(mem2.getName());

上記のようにmem2にgetName関数を追加してもmem1には反映されません。

これはJavaScriptはプロトタイプ型のオブジェクト指向であり、同じクラスから生成されたインスタンスが同じプロパティを持っているとは限らないからです。

しかしprototypeで関数を定義した場合は暗黙的な参照が行われるため、結果的に全てのインスタンスでメソッドが利用できる形になります。

プロパティをprototypeで定義した場合

var Member = function (firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

var mem1 = new Member('田中', '太郎');
var mem2 = new Member('山田', '二郎');

Member.prototype.sex = '男';
console.log(mem1.sex);//男
console.log(mem2.sex);//男

mem2.sex = '女';
console.log(mem1.sex);//男
console.log(mem2.sex);//女

上記のようにprototypeを利用し性別に関するプロパティsexを定義しました。

  1. mem1もmem2もsexプロパティは暗黙的に『男』となる
  2. mem2のプロパティに『女』を追加
  3. mem1は男、mem2は女となる

このように、prototypeでプロパティを定義すれば全てのインスタンスが同じ値をとる訳ではありません。

図解するとこのようになります。

まずはじめに自身にsexプロパティが存在するかの確認をし、存在しなければ暗黙的にクラスに定義されたsexプロパティを参照するため、このような挙動となります。

  1. メソッドはprototype
  2. プロパティはクラスに定義

が良いですね。

まとめ

prototypeを利用しメソッドを定義することによりより

  1. メモリ使用量の節減
  2. 動的にメソッドを追加しインスタンスへ共有

が可能であるということがわかりました。