EC studio EC studio 技術ブログ

JavaScriptはプロトタイプベースのオブジェクト指向言語で、
PHPやJava、C++などのクラスベースのオブジェクトとは
特徴や書き方が大きく異なります。

今回は、PHPでオブジェクト指向開発の経験がある人向けに、
JavaScriptでのオブジェクト指向プログラミングの書き方を解説します。

サンプルはすべてそのまま実行できるので、
FirebugなどのJavaScriptコンソールで実行して
挙動を確かめながら読み進めるとわかりやすいかと思います。

JavaScript の特徴

すべてがオブジェクト

JavaScriptは完全なオブジェクト指向言語であり、
すべての型の変数、関数はオブジェクトです。
(プロパティやメソッドを持つ)

JAVASCRIPT:
  1. var test = "文字列";
  2.  
  3. alert(test.length); //3

下記の変数1と変数2の二つの書き方は同じ意味です。
(厳密にはtypeof関数の返り値などが違うが、実用上ほぼ同じ)

JAVASCRIPT:
  1. //数値型
  2. var num1 = 123;
  3. var num2 = Number(123);
  4.  
  5. //文字列型
  6. var str1 = "ABC";
  7. var str2 = new String("ABC");
  8.  
  9. //配列型
  10. var arr1 = [1,2,3]
  11. var arr2 = new Array();
  12. arr2[0] = 1;
  13. arr2[1] = 2;
  14. arr2[2] = 3;
  15.  
  16. //オブジェクト型
  17. var obj1 = {name:"Masaki"}
  18. var obj2 = new Object();
  19. obj2.name = "Masaki";

この様に、数値、文字列、配列などもオブジェクトとして表現できます。

関数もオブジェクト

実は、関数も変数と同じで、

JAVASCRIPT:
  1. function func1(){
  2.     alert('A');
  3. }
  4.  
  5. var func2 = function(){
  6.     alert('A');
  7. }
  8.  
  9. func1(); //A
  10. func2(); //A

と変数に代入する形で表現できます。
(関数は、"定義"ではなく実体を持つ"変数")

function(){} の様な、関数名を書かない関数の定義方法を
無名関数(またはラムダ関数)といいます。
変数に代入するときには無名関数を使用します。

また、PHPでは可能なこういった構文はエラーになります

JAVASCRIPT:
  1. function test(){
  2.     alert('A');
  3. }
  4.  
  5. test = 'ABC'; //ここで関数を上書きしている!
  6.  
  7. test(); //エラー

この例でもわかる通りJavaScriptでは
関数の定義後に上書き(再定義)や書き換え、削除などが可能です。

さらに、変数と同じなのでこんなこともできます。

JAVASCRIPT:
  1. function test(){
  2.     alert('A');
  3. }
  4.  
  5. function do_func(func){
  6.     func();
  7. }
  8.  
  9. //test関数を引数に
  10. do_func(test);
  11.  
  12. //その場で無名関数を作って引数に
  13. do_func(function(){
  14.     alert('B');
  15. });

オブジェクトと同じように引数として渡すことも、返り値として返すこともできます。

内部構造を持ったオブジェクトのコピーは参照渡し

JavaScriptではPHPとは違い、配列に対しても
参照渡しでのコピー(シャローコピー)になります。

JAVASCRIPT:
  1. var arr1 = [0,1,2];
  2. var arr2 = arr1;
  3.  
  4. arr2[0] = 5;
  5.  
  6. //コピー元の配列も書き換わる!
  7. alert(arr1[0])//5
  8.  
  9. var obj = {name:"Masaki"};
  10. var obj2 = obj;
  11.  
  12. obj2.name = "Yamamoto";
  13.  
  14. //コピー元のオブジェクトも書き換わる!
  15. alert(obj.name); //Yamamoto

メソッドのないオブジェクト(PHPの連想配列)でも
シャローコピーになるので注意しましょう。

完全なコピー(ディープコピー)をするには

JAVASCRIPT:
  1. var arr1 = [0,1,2];
  2. var arr2 = [];
  3. for (var i in arr1){
  4.     arr2[i] = arr1[i];
  5. }
  6.  
  7. var obj1 = {name:"Masaki"};
  8. var obj2 = {};
  9. for (var i in obj1){
  10.     obj2[i] = obj1[i];
  11. }

の様に、forなどでコピーすればOK。
※ただし、多次元配列の様なものは再帰的に処理する必要あり

※for (X in OBJ) の構文はPHPのforeach同様に使えてとても便利です!

関数とスコープ

PHPと同様に、JavaScriptでも関数内を別スコープにすることができますが、
スコープの扱い方が大きく異なるので注意が必要です。

グローバル変数は、関数内でもそのまま参照できます。

JAVASCRIPT:
  1. //グローバル変数
  2. var test = "Masaki";
  3.  
  4. function myfunc(){
  5.     alert(test);
  6. }
  7.  
  8. myfunc()//Masaki

関数スコープ内でグローバル変数の内容を変更した場合も、
グローバル変数が書き変わります。

ローカル変数を作成したい場合は、 var を使います。

JAVASCRIPT:
  1. //グローバル変数
  2. var test = "Masaki";
  3.  
  4. function myfunc(){
  5.     var test = 'Yamamoto';
  6.     alert(test);
  7. }
  8.  
  9. myfunc()//Yamamoto
  10.  
  11. alert(test); //Masaki

var で宣言された変数は、宣言されたスコープのローカル変数となります。
var で宣言しない変数はグローバル変数になります。

ただ、関数の引数は例外で、varをつけなくてもローカル変数扱いになります。

JavaScriptにおけるクラス

オブジェクト指向におけるクラスの基本的な機能は

* プロパティ
* メソッド
* コンストラクタ
* アクセス修飾子 (public/private)
* 継承

などですが、JavaScriptでもすべてできます。

JavaScriptにはクラスは存在せず、オブジェクトと
関数を組み合わせて同様の機能を実装します。

通常PHPなどクラスベースのオブジェクト指向言語では、

クラス → インスタンス

と、クラスという"型"からインスタンスという"実体"を生成しますが、
プロトタイプベースのオブジェクト指向言語には"クラス"という
考え方は存在せず、定義=実体となります。(インスタンスしかない)

JavaScriptでは new という構文があり、クラスからインスタンス化している
ように見えますが、実際の処理は元となるオブジェクトを
コピー(+継承)して作られます。

インスタンスしか存在しないのにどう継承などを実装するのかなどは、
追って説明していきます。

プロパティ

JavaScriptでのプロパティ定義は、PHPの様に宣言は必要ありません。

JAVASCRIPT:
  1. var object = new Object();
  2.  
  3. object.name = 'Masaki';
  4. object.company = "EC studio";

インスタンス化した後で追加することもできますし、

JAVASCRIPT:
  1. var object = {
  2.     name:"Masaki",
  3.     company:"EC studio"
  4. };

と、シンタックスシュガー(簡易記法)を使って簡単に定義することもできます。

メソッド

メソッドもプロパティとほぼ同様に記述できます。

JAVASCRIPT:
  1. var object = new Object();
  2.  
  3. object.hello = function(){
  4.     alert('Hello!');
  5. }
  6.  
  7. object.hello();

シンタックスシュガーを使うと

JAVASCRIPT:
  1. var object = {
  2.     hello:function(){
  3.         alert('Hello');
  4.     }
  5. };
  6.  
  7. object.hello();

こう書けます。

プロパティと組み合わせると、

JAVASCRIPT:
  1. var object = {
  2.     name:"Masaki",
  3.     company:"EC studio",
  4.     hello:function(){
  5.         alert('Hello');
  6.     }
  7. };
  8.  
  9. object.hello(); //Hello

こう書けます。

メソッド内で自分自身のプロパティやメソッドを参照するには、this を使います。

JAVASCRIPT:
  1. var object = {
  2.     name:"Masaki",
  3.     company:"EC studio",
  4.     hello:function(){
  5.         alert('Hello ' + this.name);
  6.     }
  7. };
  8.  
  9. object.hello(); //Hello Masaki

これでかなりクラスに近くなりましたね。

コンストラクタ

オブジェクトをインスタンス化する時に実行されるメソッドであるコンストラクタは、
下記の様に実装できます。(厳密には、インスタンス化ではなくコピー+継承)

JAVASCRIPT:
  1. function MyObject(message){
  2.     alert(message);
  3. }
  4.  
  5. var object = new MyObject('Hello')//Hello

new Object() ではなく、関数をnewの対象にすることで
その関数の中身をnew実行時に実行できます。

コンストラクタに返り値を指定してもオブジェクト型以外は無視されます。
コンストラクタの返り値がオブジェクト型の場合はそのオブジェクトが
newで生成されるオブジェクトになります。

このコンストラクタの仕組みを使うと、newした時に
自動的にプロパティやメソッドを定義できる、
とても PHPのクラスに近い書き方ができます。

JAVASCRIPT:
  1. function MyObject(message){
  2.     this.name = 'Masaki';
  3.     this.company = 'EC studio';
  4.    
  5.     this.hello = function(message){
  6.         alert(message + ' ' + this.name);
  7.     }
  8.    
  9.     //helloを実行
  10.     this.hello(message);
  11. }
  12.  
  13. var object = new MyObject('Hello')//Hello Masaki
  14.  
  15. object.hello('Good morning')//Good morning Masaki

関数はObjectでもあるので、this が使えます。
コンストラクタの処理の中に thisへの代入を含めることで、
初期の定義を関数内に入れてしまうことができます。

※コンストラクタの引数である messageと、helloメソッドの引数のmessageが
同じ変数名になっていますが、helloメソッドのものは関数定義の中での
引数名なので、別のものと認識されます。

アクセス修飾子(public/private)

JavaScriptにはアクセス修飾子はありませんが、コンストラクタの例で挙げた
関数でのオブジェクト定義を使えば、プロパティやメソッドにアクセス制限を
かけることができます。

JAVASCRIPT:
  1. function MyObject(){
  2.     var self = this;
  3.    
  4.     //public
  5.     this.name = 'Masaki';
  6.     this.company = 'EC studio';
  7.     //private
  8.     var separator = ',';
  9.    
  10.     //public
  11.     this.hello = function(message){
  12.         alert(message + separator + getName());
  13.     }
  14.  
  15.     //private
  16.     var getName = function(){
  17.         return self.name;
  18.     }
  19. }
  20.  
  21. var object = new MyObject();
  22.  
  23. alert(object.separator); //undefined
  24. object.getName()//エラー

関数内にvarを使ってローカル変数による変数や関数にすることで、
外からは参照出来ない privateなプロパティ、メソッドを作ることができます。
(protectedなプロパティ、メソッドは作成できません)

private なプロパティ、メソッドは this を使わず
var を使ってローカル変数として参照します。

privateなメソッドを作るときの注意として、
メソッドのfunction定義内では this は使えません。
その場合の対策として、this を self というローカル変数にコピーして使用しています。
※privateなメソッドのfunction内のthisは、その関数自体を指すため。

この様に、this はそのメソッドの呼び出し元の主体に応じて変わってしまうので、
その場合に対応するためにvar self = this をコンストラクタで
あらかじめ定義しておくと便利です。

※privateなメソッドの場合の他に、イベントハンドラによって
メソッドが実行された場合でも this が変わってしまいます。
(クリック処理や、Ajax処理など)

これでプロパティ、メソッド、コンストラクタを実現できました。

JavaScriptには他にも様々なオブジェクトの書き方があり、
それぞれにメリット・デメリットがありますが、
クラスベースに文法が比較的近いこの記述方法が私のおすすめです。

継承

JavaScriptのオブジェクトには prototype という予約語のプロパティがあり、
そのprototypeに親となるオブジェクトを代入することで継承を実現できます。
prototype = 型 という意味。元となる型のオブジェクトを指定できます。

JAVASCRIPT:
  1. //親オブジェクト
  2. function MyObject(){
  3.     var self = this;
  4.    
  5.     //public
  6.     this.name = 'Masaki';
  7.     this.company = 'EC studio';
  8.     //private
  9.     var separator = ',';
  10.    
  11.     //public
  12.     this.hello = function(message){
  13.         alert(message + separator + getName());
  14.     }
  15.  
  16.     //private
  17.     var getName = function(){
  18.         return self.name;
  19.     }
  20. }
  21.  
  22. //子オブジェクト
  23. function MyObjectChild(){
  24.     //プロパティをオーバーライド
  25.     this.name = 'Yamamoto';
  26.     //privateはオーバーライドできない
  27.     var separator = '|';
  28.    
  29.     //メソッドを追加
  30.     this.thankyou = function(){
  31.             this.hello('Thank you');
  32.     }
  33. };
  34.  
  35. //プロトタイプ(継承元)を指定
  36. MyObjectChild.prototype = new MyObject();
  37.  
  38. var mychild = new MyObjectChild();
  39.  
  40. //子オブジェクトの中で、親オブジェクトのhelloメソッドが実行できる
  41. mychild.thankyou()//Thank you,Yamamoto

privateなメンバ(var で定義したもの)はオーバーライドできません。
これは、JavaScriptにおける var の挙動を理解していれば
何が起きているかがわかるかと思います。
(親の関数のローカル変数なので上書きできない)


JavaScriptのオブジェクト指向はPHPと比較すると複雑に見えますが、
その挙動や書き方を理解してしまえば、とても柔軟かつシンプルに記述でき、
大規模なシステム開発も十分可能なものだと思います。

オブジェクト指向でのJavaScript開発の参考になれば幸いです。


関連した記事:

■ 「日本でいちばん社員満足度が高い会社の非常識な働き方」

日本でいちばん社員満足度が高い会社の非常識な働き方

この記事へのコメント (1)

配列のコピーは
newArray=Array.apply(null,oldArray)
でいいのでは?

投稿者: 名無し | 2011/05/23 月曜日 12:38:59
コメントを投稿

取材に関するお問い合わせ


(担当:大崎)

EC studio 採用情報の詳細はこちら

投稿者
全ECスタッフ導入の
おすすめソフトウェア
人気のエントリー
カテゴリー
最近のエントリー
アーカイブ

BLOG オフィシャルブログ

社長ブログ
EC studio社長ブログ

ブログを読む

技術ブログ
技術部のブログ

ブログを読む

デザインブログ
デザイン部のブログ

ブログを読む

Copyright© EC studio, All Rights Reserved.