Photo by Flickr: slworking2's Photostream
既にJavaやPHPなどオブジェクト指向言語を生業としてやっているが、その感覚でJavaScriptを少し扱っているて、いまいちJavaScript分からないという方を対象に、それらのプログラム言語とは違う、「少し独特なJavaScriptのオブジェクト指向」について説明します。
目次
- オブジェクトの作成
- プロパティの定義と代入
- プロパティの削除
- アクセサプロパティ(getter / setter)
- クラス定義
- コンストラクタ引数
- インスタンスメソッドの定義
- クラスプロパティとクラスメソッドの定義
- クラスの継承
- クラスプロパティとクラスメソッドの継承
1. オブジェクトの作成
大かっこ({})でObjectクラスを作成することができます。また、
new演算子で特定のオブジェクトを作成することができます。
// 作成方法1({}でObjectインスタンスを作成する) var obj = {}; // 作成方法2(new演算子でインスタンスを作成する) var obj = new Objcet(); // Objcetインスタンス var person = new Person(); // Personインスタンス
2. プロパティの定義と代入
オブジェクトにプロパティに値を設定すると、プロパティ定義と値の設定ができます。var obj = {}; // プロパティが定義されていないとundefinedが返される console.log(obj.prop); // => undefined // プロパティに値を設定することで、プロパティ定義も一緒に行える obj.prop = 1; console.log(obj.prop); // => 1
オブジェクトの作成時に、プロパティを定義、設定することもできます。
// プロパティを定義、設定することができる var obj2 = { prop: 1, prop2: 'foo' }; console.log(obj2.prop); // => 1 console.log(obj2.prop2); // => 'foo'
3. プロパティの削除
delete演算子によって、プロパティを削除することができます。削除後にプロパティにアクセスすると、プロパティが定義されていないと同じように
undefinedを返します。
var obj = {}; obj.prop = 1; // プロパティの作成 delete obj.prop; // プロパティを参照すると、undefinedを返す console.log(obj.prop); // => undefined
プロパティにイコールでundefinedを設定することで、プロパティの削除をしないでください。
その場合、forループを使用してプロパティを一覧すると、undefinedを設定したプロパティも参照されてしまいます。
そのため、プロパティの削除は、delete演算子を使ってください。
4. アクセサプロパティ(getter / setter)
getとsetを使うことでアクセサプロパティを定義することができます。
var circle = { redius : 1, // 半径 get diameter() { return this.radius * 2; }, // 直径は半径から算出 set diameter(value) { this.radius = value / 2; } // 直径から半径を算出 }; circle.diameter = 5; // set diameter が呼ばれる console.log(circle.radius); // => 2.5 console.log(circle.diameter); // => 5 (get diameterが呼ばれる)
5. クラス定義
JavaScriptのクラス定義は、コンストラクタ内でプロパティを定義することで行います。// Personクラス(コンストラクタ)の定義 var Person = function(name, age) { this.name = name; this.age = age; } // Personインスタンスの作成 var satoshi = new Person('サトシ', 28); console.log(satoshi.name); // => 'サトシ' console.log(satoshi.age); // => 28 console.log(satoshi instanceof Person) // => true (satoshiはPersonクラスのインスタンス)
6. コンストラクタ引数
JavaScriptでは、関数呼び出し時に引数が省略できます。コンストラクタも関数であるため、コンストラクタ引数も省略できます。つまり、引数が省略されてコンストラクタが呼び出されてインスタンスが作成されると、プロパティが未定義(
undefined)のままになってしまい、予期せぬエラーが発生する可能性がでてきてしまいます。そのため、対処法として、
- プロパティを特定の値で初期化する
- エラーを発生させプログラムを中断する
という方法が考えられます。
プロパティを特定の値で初期化する
var Person = function(name, age) { // 特定の値で初期化する this.name = name || 'No name'; this.age = age || 20; } // コンストラクタ引数を指定しないと「特定の値」で初期化される var satoshi = new Person(); console.log(satoshi.name); // => 'No name' console.log(satoshi.age); // => 20 // コンストラクタ引数を渡すと「渡した値」で初期化される var satoshi = new Person('サトシ', 28); console.log(satoshi.name); // => 'サトシ' console.log(satoshi.age); // => 28
エラーを発生させプログラムを中断する
var Person = function(name, age) { // 引数をチェックする if (name == undefined) { throw new Error("引数'name'を指定してください。"); } if (age == undefined) { throw new Error("引数'age'を指定してください。"); } this.name = name; this.age = age; } // コンストラクタ引数を指定しないとエラーが発生する var satoshi = new Person(); // => Error: 引数'name'を指定してください。
7. インスタンスメソッドの定義
JavaScriptでオブジェクトにメソッドを定義する方法には次の2つがあります。functionをプロパティに代入するprototypeという特殊なオブジェクトを利用する
functionをプロパティに代入する
var Person = function(name, age) { this.name = name; this.age = age; // functionをプロパティに代入する this.greet = function() { console.log('Hello, ' + this.name); } } var satoshi = new Person('サトシ', 20); // メソッドとして呼ぶことができる satoshi.greet(); // => 'Hello, サトシ'
この方法の問題点は、new Person(...)でインスタンスを作るたびに、greet()メソッドもその数だけ作成されてしまいます。そのため、インスタンスを作るたびにメモリを無駄に消費してしまいます。
この解決方として、次の「prototypeという特殊なオブジェクトを利用する」方法があります。
prototypeという特殊なオブジェクトを利用する
var Person = function(name, age) { this.name = name; this.age = age; } // すべてのクラスが持つ特別なprototypeオブジェクトにメソッドを代入する Person.prototype.greet = function() { console.log('Hello, ' + this.name); } var satoshi = new Person('サトシ', 20); // メソッドとして呼ぶことができる satoshi.greet(); // => 'Hello, サトシ'
こうすることで、すべてのPersonインスタンスは、Person.prototype.greet()を参照するので、インスタンスを作成するたびに無駄なメモリ消費をしなくなります。
JavaScriptの処理の内部処理の順序は、greet()メソッドが呼ばれると、まずPersonインスタンス内に定義されたプロパティを検索し、見つからないのでPerson.prototype内を検索し、見つかったのでそのgreet()メソッドを実行しています。
8. クラスプロパティとクラスメソッドの定義
JavaScriptのクラスメソッドは、クラスプロパティの定義と同じように定義できます。var Person = function(name, age) { this.name = name; this.age = age; } // クラスプロパティ(定数)の定義 // 何歳以下をyoung(若い)とするかを保持する定数 Person.YOUNG_LIMIT_AGE = 20; // クラスメソッドを定義 // youngの場合 true, そうでない場合 false を返す Person.isYoung = function(age) { if (age <= Person.YOUNG_LIMIT_AGE) { return true; } return false; } // クラスプロパティを呼び出す console.log(Person.YOUNG_LIMIT_AGE); // => 20 // クラスメソッドを呼び出す console.log(Person.isYoung(10)); // => true console.log(Person.isYoung(30)); // => false
定数は慣例で大文字で定義していますが、Rubyのように変更不可能になるという言語仕様はありません。
あくまで大文字にすることで定数として変更してはいけないという慣例があるだけで、変更は可能です。(変更しないでください!)
9. クラスの継承
JavaScriptでは、継承のための構文は用意されていません。次のような方法で継承を行います。- プロパティの継承は、
applyメソッドを呼び出す - メソッドの継承は、
prototypeオブジェクトを利用する
JavaScriptでのクラス継承はなかなか複雑なので、小規模の場合は、継承を使わないようにし、大規模の場合は、ライブラリなどを使い継承を行う方が良いらしいです。
Webや本では細かな違いがいろいろあり、下記の継承のコードは妥当でない可能性があります。
プロパティの継承は、applyメソッドを呼び出す
サブクラスのコンストラクタ内でスーパークラスのapplyメソッドを呼ぶことで、スーパークラスのプロパティをサブクラスに引き継ぎます。
// Personクラス var Person = function(name, age) { this.name = name; this.age = age; } // Personを継承したEmployeeクラス var Employee = function(name, age, jobTitle) { this.jobTitle = jobTitle; // スーパークラス(Person)のコンストラクタを呼び出す Person.apply(this, [name, age]); } var satoshi = new Employee('サトシ', 28, 'QA'); console.log(satoshi.name); // => 'サトシ' console.log(satoshi.age); // => 28 console.log(satoshi.jobTitle); // => 'QA' console.log(satoshi instanceof Employee); // => true
メソッドの継承は、prototypeオブジェクトを利用する
prototypeオブジェクトのプロトタイプチェーンの仕組みを利用して継承を行います。
// Personクラスを定義 var Person = function(name, age) { this.name = name; this.age = age; } // Person#greet()メソッドを定義 Person.prototype.greet = function() { console.log('Hello, ' + this.name); } // Personを継承したEmployeeクラスを定義 var Employee = function(name, age, jobTitle) { this.jobTitle = jobTitle; // Personのプロパティを継承 Person.apply(this, [name, age]); } // プロトタイプチェーンにPersonインスタンスを設定することで、 // Employeeインスタンスのメソッド呼び出しでは、Person.prototypeのメソッド内にメソッドがあるか検索されます。 Employee.prototype = Object.create(Person.prototype); // 一行上のコードでconstructorプロパティがPersonになってしまうので、Employeeをセットします。 Employee.prototype.constructor = Employee; var satoshi = new Employee('サトシ', 28, 'QA'); satoshi.greet(); // => 'Hello, サトシ' console.log(satoshi.constructor); // => Employeeのコンストラクタ( function(name, age, jobTitle) { ... } )
10. クラスプロパティとクラスメソッドの継承
JavaScriptではクラスプロパティやクラスメソッドの継承は一般的には行われません。これは、JavaScriptではスーパークラスのクラスプロパティやクラスメソッドをサブクラスで利用するためには、それらのプロパティ定義をサブクラスにもコピーしなければならないので、コードが重複してしまうのでよくないためです。
以上です。
参考文献
- 独習JavaScript
- メンテナブルJavaScript