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
と表示されます。
- Xxx.prototype.name3 を参照しているため
メンバの列挙
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() // わんわん