JavaScriptにおけるクラス定義

JavaScriptは、プロトタイプベースのオブジェクト指向をサポートしています。クラスベースのオブジェクト指向言語に比べて記述は大きく異なりますが、JavaScriptでもクラスを利用することができます。

クラス定義とコンストラクタ

まず、簡単なクラス定義の例から見てみます。JavaScriptでは、クラス定義のための特別な構文はなく、functionで定義した関数がコンストラクタとなり、関数名がクラス名となります。下記の例では、無名関数内にFooという名前のクラスを定義していますが、new演算子によりインスタンスを生成できることが確認できます。

JavaScript

(function(window) {
  function Foo() {
    console.log("Foo's constructor")
  }
  window.Foo = Foo;
}(window));

var foo = new Foo(); // 出力:Foo's constructor
console.log(foo instanceof Foo); // 出力:true

インスタンスに定義するpublicプロパティ

クラスのインスタンスにpublicプロパティを定義するには、コンストラクタ内でthisキーワードを使ってプロパティを定義します。コンストラクタ内でのthisは、クラスのインスタンス自身を表します。下記の例では、インスタンスのpublicプロパティに外部からアクセスできることが確認できます。

JavaScript

(function(window) {
  function Foo() {
    this.property_1 = 'property_1';
  }
  window.Foo = Foo;
}(window));

var foo = new Foo();
console.log(foo.property_1); // 出力:property_1

prototypeによるpublicメソッド

クラスのインスタンスにpublicメソッドを定義するには、クラスのprototypeオブジェクトにメソッドを定義します。prototypeは、クラスの基になるオブジェクトで、そのクラスから作られる全てのインスタンスで共有されます。prototypeのプロパティとして、関数オブジェクトを代入することにより、インスタンスにpublicメソッドを追加することができます。

JavaScript

(function(window) {
  function Foo() {
  }
  Foo.prototype.method_1 = function() {
    console.log('method_1');
  };
  window.Foo = Foo;
}(window));

var foo = new Foo();
foo.method_1(); // 出力:method_1

インスタンスに定義するpublicメソッド

publicメソッドは、プロパティと同じようにコンストラクタ内でthisキーワードを使用してインスタンス自身に定義することもできます。しかし、prototypeは前述したように全てのインスタンスで共有されるため、メモリ効率化の面ではprototypeに定義した方が有利です。

JavaScript

(function(window) {
  function Foo() {
    this.method_1 = function() {
      console.log('method_1');
    };
  }
  window.Foo = Foo;
}(window));

var foo = new Foo();
foo.method_1(); // 出力:method_1

prototypeによるpublicプロパティ

publicプロパティは、前述したようにインスタンス自身に定義するだけでなく、prototypeに定義することもできます。しかし、インスタンスに定義したプロパティとprototypeに定義したプロパティには、振る舞いに違いがある点には注意が必要です。下記の例では、インスタンスとprototypeにそれぞれプロパティを定義し、値を書き換えた後でdeleteで消去していますが、結果が異なります。

JavaScriptでは、プロパティが参照された際、まずインスタンス自身のプロパティを探し、見つからない場合にprototypeのプロパティを探します。また、prototypeのプロパティは読み取り専用で、代入やdeleteはインスタンスに対して行われます。この特性をよく理解した上で、例えば定数のような扱いのプロパティはprototypeに定義するなど、適宜使い分けると良いでしょう。

JavaScript

(function(window) {
  function Foo() {
    this.property_1 = 'property_1';
  }
  Foo.prototype.property_2 = 'property_2';
  window.Foo = Foo;
}(window));

var foo = new Foo();
foo.property_1 = 'overwrite property_1';
foo.property_2 = 'overwrite property_2';
console.log(foo.property_1); // 出力:overwrite property_1
console.log(foo.property_2); // 出力:overwrite property_2
delete foo.property_1;
delete foo.property_2;
console.log(foo.property_1); // 出力:undefined
console.log(foo.property_2); // 出力:property_2

prototypeの動的拡張

prototypeは、動的に拡張することもできます。下記の例では、クラスをインスタンス化した後でprototypeにメソッドを追加していますが、メソッド追加前にインスタンス化されたdogからも追加したメソッドを利用できることが確認できます。

JavaScript

(function(window) {
  function Animal(name) {
    this.name = name;
  }
  Animal.prototype.cry = function() {
    console.log('cry');
  };
  window.Animal = Animal;
}(window));

var dog = new Animal('dog');
Animal.prototype.walk = function() {
  console.log('walk');
};
var cat = new Animal('cat');
dog.walk(); // 出力:walk
cat.walk(); // 出力:walk

hasOwnProperty()による判定

hasOwnProperty()を使うことで、インスタンスに定義されたプロパティかprototypeに定義されたプロパティかを判別することができます。下記の例でわかるように、プロパティがインスタンスに定義されている場合のみ、trueを返します。

JavaScript

(function(window) {
  function Foo() {
    this.property_1 = 'property_1';
  }
  Foo.prototype.property_2 = 'property_2';
  window.Foo = Foo;
}(window));

var foo = new Foo();
console.log(foo.hasOwnProperty('property_1')); // 出力:true
console.log(foo.hasOwnProperty('property_2')); // 出力:false

privateプロパティ・privateメソッド

privateプロパティを定義するには、コンストラクタ内で変数をvar宣言します。また、privateメソッドもコンストラクタ内でvar宣言した変数に関数オブジェクトを代入するか、コンストラクタ内でfunctionキーワードで定義します。これらのprivateメンバには、外部からはもちろん、prototypeに定義したpublicメソッドからもアクセスできません。privateメンバにアクセスできるのは、コンストラクタ内か後述する特権メソッドだけです。

JavaScript

(function(window) {
  function Foo() {
    var property_1 = 'property_1';
    var method_1 = function() {
      console.log('method_1');
    };
    function method_2() {
      console.log(property_1);
    }
    method_2();
  }
  Foo.prototype.method_3 = function() {
    console.log(property_1);
  };
  window.Foo = Foo;
}(window));

var foo = new Foo(); // 出力:property_1
console.log(foo.property_1); // 出力:undefined
//foo.method_1(); // エラー
//foo.method_2(); // エラー
//foo.method_3(); // エラー

特権メソッド(プリビレッジドメソッド)

コンストラクタ内でthisキーワードで定義されたpublicメソッドは、privateメンバにアクセスすることができます。このようなメソッドは、特権メソッド(プリビレッジドメソッド)と呼ばれます。

JavaScript

(function(window) {
  function Foo() {
    var property_1 = 'property_1';
    this.getProperty_1 = function() {
      return property_1;
    };
    this.setProperty_1 = function(value) {
      property_1 = value;
    };
  }
  window.Foo = Foo;
}(window));

var foo = new Foo();
foo.setProperty_1('overwrite property_1');
console.log(foo.getProperty_1()); // 出力:overwrite property_1

staticプロパティ・staticメソッド

クラスにstaticプロパティやstaticメソッドを定義するには、クラスオブジェクトに対して直接プロパティやメソッドを追加します。これらのstaticプロパティ・staticメソッドは、インスタンス化することなく、クラスを通して直接アクセスできます。

JavaScript

(function(window) {
  function Foo() {
  }
  Foo.property_1 = 'property_1';
  Foo.method_1 = function() {
    console.log('method_1');
  };
  window.Foo = Foo;
}(window));

console.log(Foo.property_1); // 出力:property_1
Foo.method_1(); // 出力:method_1