オブジェクト指向(プロトタイプ, クラス構文)

JavaScriptのオブジェクト指向は「プロトタイプベースで実装する方法」と「ES2015から導入されたクラス構文を利用して実装する方法」が存在します。各実装方法について確認します。

プロトタイプベースで実装

functionでコンストラクタ定義

function を通じて、インスタンスを生成するためのFunctionコンストラクタを定義します。

function Animal(options) {
  var tmp = 20;
  this.name = options.name;
  this.old = options.old;
}
 
// prototypeを利用することでメモリ節約
Animal.prototype = {
  showName: function () {
    console.log(this.name);
  },
  showOld: function () {
    console.log(this.old);
  }
}
 
var a = new Animal({name: 'ジョン', old: 22});
a.showName();  // ジョン
a.showOld();   // 22
console.log(a.tmp)  // undefined
console.log(a.name) // ジョン

コンストラクタを new演算子 で呼び出し、インスタンスを生成します。

コンストラクタ配下内で this.プロパティ名 と定義することで、インスタンスのプロパティを設定できます。

コンストラクタ配下内で this.name と定義しているためインスタンスにnameプロパティが設定されています。

コンストラクタ配下内で定義している tmp変数 は単なるAnimalコンストラクタ内でのローカル変数のため、インスタンスには設定されません。

prototypeプロパティについて

prototypeプロパティは、関数(Functionオブジェクト)が生成されたときに自動的に作られるプロパティで、constructorプロパティのみをもつオブジェクトを参照しています。

prototypeプロパティが参照するオブジェクトに設定したメンバは、インスタンス化されたオブジェクトで暗黙的に参照されます。動作確認します。

var Person = function() {};   
Person.prototype.name = "鈴木";
Person.prototype.chgName = function(name) {
    Person.prototype.name = name;
};
 
// ★1
var p1 = new Person();
var p2 = new Person();
console.log(p1.name);          // 鈴木
console.log(p2.name);          // 鈴木
 
// ★2
p1.name = '山田';
console.log(p1.name);          // 山田
console.log(p2.name);          // 鈴木

// ★3
p2.chgName('佐藤');            
console.log(p1.name);          // 山田
console.log(p2.name);          // 佐藤 
 
// ★4
delete p1.name;                
console.log(p1.name);          // 佐藤
console.log(p2.name);          // 佐藤
  • ★1
    • p1インスタンス、p2インスタンスともにnameプロパティを設定していませんがnameプロパティにアクセスすると『鈴木』と表示されます。インスタンス化されたオブジェクトでプロパティが設定されていない場合、prototypeオブジェクトのプロパティを参照するためです。
  • ★2
    • p1インスタンスのnameプロパティを設定してます。設定を行うと、p1インスタンス自身がnameプロパティを持つようになるのでprototypeオブジェクトのnameプロパティを参照しなくなります。
  • ★3
    • prototypeオブジェクトのnameプロパティの値を書き換えてます。p2インスタンスはprototypeオブジェクトのnameプロパティを参照するため『佐藤』と表示されるようになりました。
  • ★4
    • delete演算子 でp1インスタンスのnameプロパティを削除してます。その後、p1インスタンスでnameプロパティにアクセスすると『佐藤』と表示されるようになりました。これはp1インスタンスでnameプロパティを持たなくなり、prototypeオブジェクトのnameプロパティを参照するようになったためです。

プライベートなプロパティ・メソッド

プライベートプロパティ プライベートメソッド を利用したいのであれば以下のようにします。

function Animal() {
  // プライベートプロパティ
  var _name;
  var _old;
   
  // プライベートメソッド
  var _checkOld = function(old) {
    return (old > 0);
  }
   
  // パブリックメソッド(プライベート変数にアクセス)
  this.setName = function(name) {
    _name = name;
  }
  this.getName = function() {
    return _name;
  }
   
  this.setOld = function(old) {
    if(_checkOld(old)) {
      _old = old;
    }
  }
  this.getOld = function() {
    return _old;
  }
}
 
// パブリックメソッド(プライベート変数にアクセスしない)
Animal.prototype = {
  showName: function() {
    console.log(this.getName());
  },
  showOld: function() {
    console.log(this.getOld());
  }
}
 
var a = new Animal();
a.setName('ジョン');
a.setOld(22);
 
a.showName();  // ジョン
a.showOld();   // 22

継承

子クラスのprototypeプロパティに、親クラスのインスタンスを設定することでクラスが継承されます。動作確認します。

var Xxx = function() {console.log("Xxx create");};
Xxx.prototype.name1 = 'xxx1';
Xxx.prototype.name2 = 'xxx2';
Xxx.prototype.name3 = 'xxx3';
Xxx.prototype.fX = function() {console.log("fX");};
var x =  new Xxx();                                  // Xxx create 
console.log(Xxx.prototype);                          // [object Object]
console.log(Xxx.prototype.constructor);              // function() {console.log("Xxx create");} 
console.log(x.name1);                                // xxx1
console.log(x.name2);                                // xxx2
console.log(x.name3);                                // xxx3
 
var Yyy = function() {console.log("Yyy create");};
Yyy.prototype = new Xxx();                           // Xxx create        ★1 
Yyy.prototype.name2 = 'yyy2';
Yyy.prototype.name3 = 'yyy3';
Yyy.prototype.fY = function() {console.log("fY");};
var y =  new Yyy();                                  // Yyy create
console.log(Yyy.prototype);                          // [object Object] 
console.log(Yyy.prototype.constructor);              // function() {console.log("Xxx create");} 
console.log(y.name1);                                // xxx1
console.log(y.name2);                                // yyy2 
console.log(y.name3);                                // yyy3 
 
var Zzz = function() {console.log("Zzz create");};
Zzz.prototype = new Yyy();                           // Yyy create        ★2
Zzz.prototype.name3 = 'zzz3';
Zzz.prototype.fZ = function() {console.log("fZ");};
var z =  new Zzz();                                  // Zzz create 
console.log(Zzz.prototype);                          // [object Object] 
console.log(Zzz.prototype.constructor);              // function() {console.log("Xxx create");} 
console.log(z.name1);                                // xxx1
console.log(z.name2);                                // yyy2
console.log(z.name3);                                // zzz3
 
console.log(z.constructor);                          // function() {console.log("Xxx create");} 
console.log(z.constructor.prototype);                // [object Object]  
console.log(z.constructor.prototype.name3);          // xxx3              ★3
  • ★1
    • XxxクラスをYyyクラスが継承してます。
  • ★2
    • YyyクラスをZzzクラスが継承してます。
  • ★3
    • Xxx.prototype.name3 を参照しているため xxx3 と表示されます。

メンバの列挙

for in文を使って、オブジェクト内に存在するプロパティを取り出してみます。

var Xxx = function() {console.log("Xxx create");};
Xxx.prototype.name1 = 'xxx1';
Xxx.prototype.name2 = 'xxx2';
Xxx.prototype.name3 = 'xxx3';
Xxx.prototype.fX = function() {console.log("fX");};
var x =  new Xxx();                                  // Xxx create 
 
var Yyy = function() {console.log("Yyy create");};
Yyy.prototype = new Xxx();                           // Xxx create
Yyy.prototype.name2 = 'yyy2';
Yyy.prototype.name3 = 'yyy3';
Yyy.prototype.fY = function() {console.log("fY");};
var y =  new Yyy();                                  // Yyy create
 
var Zzz = function() {console.log("Zzz create");};
Zzz.prototype = new Yyy();                           // Yyy create
Zzz.prototype.name3 = 'zzz3';
Zzz.prototype.fZ = function() {console.log("fZ");};
var z =  new Zzz();                                  // Zzz create 
z.name4 = 'zzz4';
 
 
 
//### プロパティのみを列挙 ###
tmp = "";
for (name in z) {
    if (typeof z[name] !== 'function') {
        tmp += name + ':' + z[name] + '    ';  
    }
}
console.log(tmp);                                    
// name3:zzz3    name2:yyy2    name1:xxx1 
 
 
 
//### メソッドのみを列挙 ###
tmp = "";
for (name in z) {
    if (typeof z[name] === 'function') {
        tmp += name + ':' + z[name] + '    ';  
    }
}
console.log(tmp);
// fZ:function() {console.log("fZ");}    fY:function() {console.log("fY");}    fX:function() {console.log("fX");} 
 
 
 
//### zインスタンスがもつプロパティのみを列挙 ###
tmp = "";
for (name in z) {
    if (z.hasOwnProperty(name)) {
        tmp += name + ':' + z[name] + '    ';  
    }
}
console.log(tmp); 
// name4:zzz4  

不必要なプロパティを取り除くには、typeof演算子hasOwnPropertyメソッド を利用します。hasOwnPropertyメソッド は、オブジェクト自身にプロパティがあるかどうかを判断します。

クラス構文で実装

ES2015からクラス構文でも書けるようになりました。

クラス定義

// animal.js
class Animal {
  constructor(options) {
    this.name = options.name
    this.old = options.old
  }
 
  showName() {
    console.log(this.name)
  }
   
  showOld() {
    console.log(this.old)
  }
}
export default Animal

定義したクラスからインスタンスを生成してみます。

import Animal from './animal.js'
const a = new Animal({name: 'ジョン', old: 22})
a.showName()  // ジョン
a.showOld()   // 22

setter・getter

class Animal {
  set name(value) {
    this._name = value
  }
  
  get name() {
   return this._name
  }
}

const a = new Animal()
console.log(a.name)   // undefined
a.name = 'ジョン'
console.log(a.name)   // ジョン
console.log(a._name)  // ジョン

getter経由でなくても取得できてしまうので注意が必要です。

静的メソッド|static

static で静的メソッド(インスタンスを生成せずに呼び出すメソッド)を定義できます。

class Animal {
  static bark() {
    console.log('わんわん')
  }
}

Animal.bark()  // わんわん

継承|extends

クラスは extends で継承させることもできます。

class Animal {
  constructor(options) {
    this.name = options.name
    this.old = options.old
  }
 
  showName() {
    console.log(this.name)
  }
   
  showOld() {
    console.log(this.old)
  }
}
 
class Dog extends Animal {
  constructor(options) {
    super(options)
  }
   
  bark() {
    console.log('わんわん')
  }
}
 
const a = new Dog({name: 'ジョン', old: 22})
a.showName()  // ジョン
a.showOld()   // 22
a.bark()      // わんわん