読者です 読者をやめる 読者になる 読者になる

JavaScriptのオブジェクト指向入門

ちょっと色々なサイトで取り上げられているので二番煎じですがまとめておく必要がでてきたのでポストします。

JavaScript言語概要

JavaScript は 手続き型 (procedural) 言語と オブジェクト指向 (object oriented) 言語の両方として機能させられる。 オブジェクトは JavaScript では、 C++Java のようなコンパイラ型言語において一般的な構文クラス定義と反対に、 実行時にメソッドとプロパティをそれ以外からのオブジェクトに加えることで プログラムに基づいて作成される。一度オブジェクトが構築されると、 同じようなオブジェクトを作成するのに青写真 (あるいは プロトタイプ) として使える。

Mozilla Japan, Mozilla Foundation and Mozilla Corporation 2004-2006 JavaScript について - JavaScript | MDN

JavaScriptにおけるリテラルの振舞

JavaScriptにはfunction,string,array,objectなど様々なリテラルが存在しますがそれらリテラルは全てオブジェクトとして取り扱うことが可能。例えばfoo = 'foo'; という文字列リテラルを作成した場合でも foo.bar = 'bar'; といったように新たにプロパティを追加するこも可能でありまたメソッドの追加も同様に可能。文字列リテラルであればString.prototypeに定義されるものがデフォルトで利用可能です(indexOf, replaceなどのメソッド)

JavaScriptオブジェクト指向プログラミング

クラスの作成

classという概念がないのでなんとも説明しにくいですがfunctionオブジェクトとnewを利用することによりクラスのようなものを作成することが可能です。以下のように定義した場合は,Personがコンストラクタとしてインスタンスshogoを作成するときにfunction Person(){};が呼び出されるPerson.prototypeで定義されたプロパティやメソッドインスタンスで利用可能である。
この際はfunction Person(){}内で戻り値を指定してはいけない。もし戻り値を指定した場合はPerson.prototypeで定義されたメソッド・プロパティは利用できない。

function Person(){};
Person.prototype.__age__ = 20;
Person.prototype.setAge = function(age){ this.__age__ = age; };
Person.prototype.getAge = function(){ return this.__age__; };
Person.Foo = 'foo';
Person.Bar = 'bar';

shogo = new Person; // 引数必要ないのであれば new Person()としなくてもいい。 
継承など

継承に関しては以下のようなコードで説明ができる。以下ではコンストラクタなど親クラスで定義された親クラスのメソッドの呼び出しができないので別な実装が必要。prototypeに対してnew Personを代入して浅いコピーを行なっている。ただし、prototypeを用いた下記のような継承ではPersonのPerdon.FooやPerson.Barなどは継承されない。

function Fighter(){};
Fighter.prototype = new Person;
Fighter.prototype.weapon = 'Sword';
Fighter.prototype.attack = function(){ alert(this.weapon); };

shogo4405 = new Fighter;
publicやprivateなどのカプセル可

基本的には概念としては存在しないがClosure(クロージャ)を利用することで対応可能。JavaScriptではクロージャを利用することによって多様な表現ができる。

function Person()
{
  var age = 20;

  return {
    setAge : function(num){ age = num; },
    getAge : function(){ return age; }
  };
};

var shogo4405 = new Person();
alert(shogo4405.getAge()); // 20
shogo4405.setAge(21);
alert(shogo4405.getAge()); // 21

alert(typeof(age)); // undefined
thisの取扱

解説書によっては thisは自信を表すオブジェクトの参照 であるという説明がされている。この説明で問題はないがJavaScritの全てのメソッドはfunctionオブジェクトであるが故に別のオブジェクトに代入することが可能である。その際、参照されるthisは代入後のオブジェクトになるため thisが参照するのは.(ドット)演算子の一つ前のオブジェクトを参照する と定義すると理解しやすい。

foo = {};
foo.prop = 'foo';
foo.method = function(){ alert(this.prop); };
alert(foo.method()); // foo

bar.prop = 'bar';
bar.method = foo.method;
bar.func = function(){ for(i in this) document.write(i,','); };

alert(bar.method()); // bar

さてthisは.(ドット)の前のオブジェクトを参照すると定義したが.(ドット)の前がない場合は何を参照するかと言えば大域スコープのオブジェクトを参照する。(IEFirefoxでいうところのwindowオブジェクト)

func = bar.func;
func();

Packages,sun,java,netscape,XPCNativeWrapper,GeckoActiveXObject,Components,parent,top,scrollbars,name,scrollX,scrollY,scrollTo,scrollBy,getSelection,scrollByLines,scrollByPages,sizeToContent,dump,setTimeout,setInterval,clearTimeout, ...(略) Firefoxによる出力結果。

継承のいくつかの実装

最も一般的な継承パターン
function Super(){};
Super.prototype = {prop1:1,prop2:2};
function Basic(){};
Basic.prototype = new Super;
任意のオブジェクトを継承する
function inherit(o)
{
  var f = function(){};
  f.prototype = o;
  return new f;
};

Foo = {};
Foo.prop1 = 'prop1';

Bar = inherit(Foo);
Bar.prop2 = 'prop2';

alert(Bar.prop1); // prop1
Foo.prop1 = 'foo';
alert(Bar.prop1); // foo
alert(Foo.prop2) // undfined

親クラスのコンストラクタ含めて継承

参考文献・引用文献

加筆・訂正など

  • 10月7日 初稿ポスト(思いっきり書きかけ)