2009년 11월 15일 일요일

Eclipse에 Aptana PlugIn 추가

Eclipse에 Aptana PlugIn 추가

  1. Go to Help -> Install New Software...
  2. In the "Work with:" text box, enter: http://update.aptana.com/install/studio
  3. Click Add...
  4. Specify the Name of the Update site (i.e. Aptana Studio Plugin)
  5. Click OK
  6. Select the "Aptana Studio" checkbox
  7. Click the Next Button
  8. Confirm the items you are installing
  9. Click the Next Button once more
  10. Accept the terms and conditions
  11. Click the Finish Button

 

출처: http://dancer.tistory.com/112

 

현재

Eclipse Plug-in 형태로 Aptana Editor를 사용중에 있습니다.
팀원분을 통해서, Aptana Editor에서 User Javascript File Reference 를 사용할 수 있다는 사실을
어제 알게 되었습니다.  -0-;


사용방법도 매우 간단합니다.(Eclipse 기준)
Window - Show View - Other - Aptana Standard Views - References - OK

그리고 Default Profile에  Reference로 사용하실 파일을 추가해주시면 됩니다.
아래 첨부된 그림파일을 보시면 대충 어떻게 동작하는지 알 수 있습니다.

aptana 소개

APTANA를 소개합니다.
Javascript, AIR, Apple iphone,php, ruby 등을 지원합니다. 나온지는 꽤 되었습니다. 작년 말정도에 정식으로
Release가 되었던 걸로 기억합니다.

Javascript를 코딩할 때 정말 굉장히 유용합니다. 코드 어시스트를 해주는 것은 물론이거니와 현재 내가 코딩하고 있는 페이지에 에러까지 실시간으로 보여주어 마치 자바를 코딩하는 것 같은 느낌도 들 정도입니다.

그리고 Adobe의 JSEslipse보다 뛰어난 코드 어시스트를 해줍니다. Aptana를 쓰기 전까지만 해도 JsEclipse를 사용했었는데 Aptana없이 이젠 코드짜는게 너무 불편해져 버렸습니다.

Javascript를 코딩하기 어려워 하는 이유중에 하나가 외워서 코딩해야 하는 것이죠. 어디에 무엇이 있는지
속속들이 알아야만 효율적인 개발이 가능합니다. 그런데 Aptana를 사용하면 70%정도는 커버가 될거 같습니다.
어디에 누가 있는지 정확히는 아니어도 어느정도 어시스트를 해줍니다.
불문이 불여 일견이라고 했습니다 ^^

aptana.com으로 가셔서 다운받으시면 되고 community버전은 기능의 제약이 다소 있으나 무료입니다.
그리고 코딩용으로만 사용할 거라면 무료로도 무리없이 사용 할 수 있습니다. :)



일단 Aptana는 이클립스 기반입니다 Javascript를 CVS나 SVN으로 관리하기도 굉장히 용이합니다.
예제로 간단한 사용법을 기술해 보겠습니다.

일단 프로젝트를 하나 만듭니다.


AIR 프로젝트는 Adobe의 AIR Application으로 개발이 가능하도록 해주는 프로젝트이며 , 지금은
JavaScript에 관해 진행할 것이므로 Default Web Project를 클릭하여 웹 프로젝트를 선택후 Next를 클릭합니다.


 세계적으로 유명한 Javascript Library를 자동으로 mport해서 프로젝트를 만들 수 있습니다.
항상 사용하고 있는 Javascript Library가 있다면 선택해 주세요.
전 Mootool을 사용하므로  Mootool을 임포트 하겠습니다. 만약 사용하는 Library가 업다면 체크없이 Next를
클릭하시면 됩니다.

Aptana에서 Jaxer라는 Ajax용 서버를 사용하겠냐는 과정이 나타납니다.
쉽게 말하면 Jaxer란 것은 Javascript와 CSS , HTML을 서버사이드 스크립트로 받아들이는 서버로써
Javascript가 서버사이트 스크립트가 되어 파일을 릭고 쓰고 DB에 접속하고 하는 것을 가능하게 해줍니다.
또한 소켓접속도 가능하게 해주죠..정말 놀라운 기술입니다. Javascript를 좋아하는 저로써는 무한한 가능성을
보고 있습니다. Jaxer는 html을 읽을때 Javascript를 파싱하면서 해당 JS가 서버용인지 client용인지를 파악하여 javascript를 파싱합니다. 어쩌면 Google의 gears와 닮은면이 조금은 있습니다.

Jaxer만 설명하려면 너무 깊이 가야 하므로 간단한 소개로 마무리 하고 다음에 자세히 다루어 보도록 하겠습니다.


별도의 로컬서버나 서버의 경로를 설정해주고 해당 서버쪽의 파일을 편집한다고 한다면 위의 설정을 보시고
설정해 주시면 쉽게 HTML 미리보기를 할 수 있습니다.
일단 JS의 코딩이 얼마나 편해지는가를 보기 위함이므로 패스. finish를 꾹 누르고 프로젝트를 생성합니다.

프로젝트가 생성되었습니다. 자세히 살펴 보시죠.
Mootool을 선택해서 자동으로 프로젝트가 생성되었습니다.
sampe page도 하나 생겼군요.

mootools_sample.htm을 열어서 편집코드에서 "$"를 입력해보았습니다. 그러면 아래와 같이 어시스트가 나타납니다.


해당 Method들이 어떤 파일에 있는지도 나타나게 됩니다.
일단 이런 어시스트가 가능한건 Assist View쪽을 보면 Assist할 js파일이나 html,css파일이 등록되어 있기 떄문입니다.













그럼 간단하게 위의 html파일에서 사용한 test_assist.js란 파일을 /lib/mootools/ 에 등록해보겠습니다.


이제 간단하게 파일을 만들어서 등록했고 assist가 제대로 되는지 확인해 보겠습니다.


mootools_sample.htm파일에 javascript를 load하는 스크립트 한줄을 넣었으니 자동으로 추가된 js파일의 내용대로 어시스트 되어야 합니다.

그럼 제대로 되는지 스크립트 블럭을 만들고 그 안에 test라고 입력해보면 자동으로 어시스트가 이루어 지는
것을 알수 있습니다.

















이처럼 htm을 file reference에 넣어두고 개발을 하면 쉽게 javascript를 알아 볼수 있고 개발이 편리하게
이루어 집니다. 대체적으로 이정도만 사용하여도 Aptana에 빠져 들게 될 것입니다.

참고사항으로 Aptana의 편집창을 보면 아래쪽에 탭이 있습니다.


소스를 선택하면 소스편집모드이며 기본적으로 파이어폭스와 익스플로러 탭이 있고 클릭을 하면
바로 미리보기가 가능합니다. 아까 HTML 미리보기 서버설정을 하셨다면 해당 서버쪽의 파일을 로드하게
될 것입니다. 브라우저를 왔다 갔다 하며 테스트 하는것도 번거로운 일중에 하나인데 +를 클릭해서
브라우저탭을 등록해 놓으면 브라우저호환성을 테스트할 때 굉장히 용이 할 것입니다.

다음번엔 Aptana로 AIR Application을 개발한다거나 Jaxer를 활용하는 걸 다루어 보겠습니다.
너무 긴 글인데 끝까지 읽어 주셔서 감사드립니다 . 소개하는 글이라 많은 내용을 적지 않으려고 했는데
복잡해진 것 같네요. 웹이 점점 변해가는 과도기 과정이네요. 좋은 툴도 나왔지만 배우지 않으면 쉽게 뒤쳐질
것 같습니다. 어려운 시기에 배우시는 분들의 권투를 빕니다.

Ajax/JavaScript開発にはAptanaが便利

Ajax/JavaScript開発にはAptanaが便利

 このままメモ帳で開発してもいいのですが、JavaScriptはデバッグが大変なので開発環境を整えましょう。本連載では、Aptanaを使います。

 正式名称は「Aptana Studio」で、EclipseベースのオープンソースWebオーサリングツールです。JavaScriptやAjaxを用いて開発するうえで役立つ強力なツールとなります。Aptanaについての詳細は、少し古いですが以下の記事も参照しておいてください。

Aptanaを使うにはJREが必要

 Aptanaを使うにはJavaの実行環境(JRE)がインストールされている必要があります。インストールされていないPCを使う場合は、@ITの「バージョン別セットアップマニュアル一覧」などを参照に最新版のJREをインストールしておいてください。

 次ページではAptanaをダウンロードしインストールして、AptanaでjQueryを使ったWebアプリケーションの作り方を説明します。

2009년 11월 14일 토요일

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

 以上、最終回の今回はJavaScriptによるオブジェクト指向について解説した。JavaScriptのオブジェクト指向は「プロトタイプ・ ベースのオブジェクト指向」と呼ばれ、従来の「クラス・ベースのオブジェクト指向」に精通した開発者であればあるほど、なじみにくく感じる概念かもしれな い。しかし、あまり難しく考える必要はない。

 繰り返しではあるが、JavaScriptではクラスという抽象的な概念の代わりに、すべての概念が実体を持ったオブジェクト(インスタンス)で 表されるというだけだ。クラス・ベースのオブジェクト指向言語では、クラスを基にオブジェクトを作成していたところが、JavaScriptではオブジェ クトを基に異なるオブジェクトを作成することになっただけなのだ。この1点さえ分かってしまえば、実は、プロトタイプ・ベースのオブジェクト指向も恐れる に足らない。

 本稿が、JavaScriptに対して苦手意識を持つ開発者諸氏にとって、これを克服するきっかけとなれば幸いである。

[コラム]オブジェクトと連想配列

 本文で、オブジェクト・リテラルの「{名前 : 値, ……}」という表記は、JavaScriptにおける連想配列(ハッシュ)の記法でもあると述べた。これを聞いて、それではオブジェクトはそもそも連想配列なのではないかと考えた方はご明察。

 そのとおり、JavaScriptではオブジェクトと連想配列との間に厳密な区別はない。便宜上、オブジェクトと連想配列を(キーワードとして) 使い分けることはあるにせよ、オブジェクトと連想配列とは、JavaScriptの世界においては同一の概念なのである。従って、以下の記述も、意味的に 完全に等価である。

var obj = new Object();
obj.x = 1;
obj.y = 2;

var obj = {x:1, y:2};

 また、オブジェクトのメンバにアクセスするにも、ドット演算子を利用するばかりではない――連想配列のブランケット構文を利用してアクセスするこ とが可能である。逆もしかり、連想配列の各キーに対してドット演算子でアクセスすることも可能だ。そう、連想配列はオブジェクトであり、オブジェクトは連 想配列であるのだから*5。従って、以下の2文は、これまた等価の命令である。

obj.x

obj["x"]

*5 もっとも、厳密にはブランケット構文とドット演算子とには違いもある。例えば、「123」のようなキー(メンバ名)はドット演算子では認められないが、ブ ランケット構文では可である。なぜなら、ブランケット構文ではキー名は文字列として扱われるが、ドット演算子では識別子(第2回を参照)として扱われるからである。つまり、ドット演算子を利用する場合、メンバ名は変数名と同様の制約を課せられることになる。

 もっと行ってみよう。実は、先ほどのリスト9でさりげなくfor…inループでオブジェクト配下のメンバを列挙するコードを表してみたが、オブ ジェクトが連想配列であることを理解してみれば、これまた直感的に理解できる内容であるはずだ。for…inループは、連想配列から順に配下のキーを取り 出しているというわけだ。

 もちろん、先ほどのリストでも示したように、取り出したキーによってブランケット構文でアクセスすることで、メンバの内容を取得することもできる。

 ただし、for…in命令による列挙は、すべてのメンバが対象となるわけではないので、注意してほしい。

var ary = new Array();
for (name in ary) { window.alert(name); }
リスト14 組み込みオブジェクトであるArrayオブジェクトのメンバを列挙する例

 このコードは、Arrayオブジェクトに属するメンバを順に列挙することを目的としたものである(Arrayオブジェクトではconcat、 join、pop、pushなどのメソッドが公開されているはずである)。しかし、結果はというと、「メンバは一切表示されない」。これはどういうことな のだろうか。

 実は、これはArrayオブジェクトのprototypeプロパティがDontEnum属性でマーキングされていることから生じる現象である。 DontEnum属性が付与されたメンバは、for…inループによる列挙の対象から外される。そのため、prototypeプロパティ(プロトタイプ・ オブジェクト)で定義されたすべてのメンバはfor…inループでは表示されないというわけだ。この事情は、ほかのオブジェクトのメンバについても同様で ある。

 ちなみに、JavaScriptではそのほかにもメンバの特性を決めるための属性として、以下のようなものを用意している。

属性名 機能概要
DontDelete delete演算子による削除の禁止
DontEnum for…inループによる列挙の対象外
Internal 内部メンバ(直接のアクセスを禁止)
ReadOnly 読み取り専用(値の設定を禁止)
JavaScriptの内部属性

 ただし、これらの属性はあくまでJavaScript内部的なもので、アプリケーション開発者が直接に触れることはできない(例えば、自作オブ ジェクトのメンバに対してDontEnum属性を追加したり、組み込みオブジェクトのメンバからDontDelete属性を外したりということはできな い)。End of Article

JavaScriptでオブジェクト指向プログラミング 3 / 4

(2)プロトタイプ・オブジェクトの変更はリアルタイムに認識

 プロトタイプ・オブジェクト配下のメンバが(インスタンスにコピーされるわけではなく)暗黙的な参照を通じて、必要都度にアクセスされるという事 実には、もう1つ大きなメリットがある。それは、インスタンスを生成した「後」に、基となるプロトタイプ・オブジェクトにメンバを追加した場合にも、これ を認識できるという点である。

 例えば、以下のような例を見てみよう。

var Animal = function() {};
Animal.prototype.name = "サチ";
var anim = new Animal();

Animal.prototype.sex = "メス"; // インスタンスの生成後にメンバを追加

window.alert(anim.sex); // 「メス」
リスト10 インスタンスの生成後にメンバを追加した例

 もっとも、この性質は、先ほどの「暗黙的な参照」を理解していれば、さほど驚くには当たらないだろう(むしろ当然ともいえる性質である)。

 以上が、プロトタイプ・オブジェクトの基本であるが、ここでもう1つ、プロトタイプ・オブジェクトをよりシンプルに定義するための記法を紹介しておく。

■プロトタイプをオブジェクト・リテラルで定義する

 ここまでのコード例では、プロトタイプ・オブジェクトに対して、ドット演算子で個々のメンバを追加してきた。もちろん、これはこれで正しい記法なのだが、メンバの数が多くなってきた場合、どうしてもドット演算子による記法ではコードが冗長になりがちだ。

 ささいなことであるかもしれないが、毎回、「Animal.prototype.メンバ名 = ~」のように記述しなければならないのはタイプ量という観点でもうれしくないし、そもそもクラス名(本稿ではAnimal)が変更になった場合に、すべて のメンバ定義について変更しなければならないという点も好ましくない。

 そこで登場するのが、オブジェクト・リテラル表現である。リテラルとは、任意の式内に直接に記述可能なデータ値(表現)のこと。本連載でも、関数 リテラルや配列リテラルについて紹介してきたが、リテラル表現を利用することで、より記述上の制約を受けずに柔軟なコードを記述できるというメリットがあ ることは、すでに実感いただけているのではないだろうか。

 オブジェクトにもリテラル表現があるというならば、これを使わない手はない。以下は、その具体的なコード――Animalクラス(Animal.prototype)に対して、リテラル表現を使って、getVoice/toStringメソッドを追加する例だ。

var Animal = function(name, sex){
  this.name = name;
  this.sex = sex;
}

Animal.prototype = {
  getVoice : function() {
    window.alert(this.name + "「チュウ!」");
  },
  toString : function() {
    window.alert(this.name + " " + this.sex);
  }
};

var anim = new Animal("トクジロウ", "オス");
anim.toString(); // 「トクジロウ  オス」
リスト11 オブジェクト・リテラルを使ったプロトタイプの定義

 なるほど、リテラル表現を利用することで、「Animal.prototype.~ =」のような式を記述する必要がなくなった分だけ、コードがすっきりと見やすくなった。

 ちなみに、オブジェクト・リテラルの「{名前 : 値, ……}」という表記は、JavaScriptにおける連想配列(ハッシュ)の記法でもある(後述のコラム「オブジェクトと連想配列」を参照)。通常、プロ トタイプ・オブジェクトに対して複数のメンバをまとめて追加する場合には、このオブジェクト・リテラルの記法を用いるのが好ましい。

■プロトタイプ・チェーン - JavaScriptの継承機構 -

 プロトタイプ・ベースのオブジェクト指向を理解するうえで、もう1つ、忘れてはならない重要なキーワードとして「プロトタイプ・チェーン」がある。プロトタイプ・チェーンとは、プロトタイプ・ベースのオブジェクト指向における継承機構であるといってもよいだろう。

var Animal = function() {}
Animal.prototype = {
  walk : function() {
   window.alert("トコトコ");
  }
};

var Hamster = function() {};
Hamster.prototype = new Animal();

Hamster.prototype.gnaw = function() {
  window.alert("ガジガジ…");
};

var ham = new Hamster();
ham.walk(); // 「トコトコ」
ham.gnaw(); // 「ガジガジ…」
リスト12 Animalクラスを継承するHamsterクラスを定義する例

 注目していただきたいのは、リスト内の太字の部分――プロトタイプ・オブジェクト(Hamster.prototype)にAnimalクラスの インスタンスを格納しているという点だ。これによって、Hamsterクラスのインスタンスでは、Animalクラスのwalkメソッドを呼び出すことが 可能になる。

 この動作は、先ほどの「暗黙的な参照」を理解していれば、その延長線上の概念として理解は容易であるはずだ。

 JavaScriptでは、まず現在のインスタンスからメンバの検索を行う。そこで該当するメンバが存在しない場合には、次にインスタンスの基と なるオブジェクトのプロトタイプから適合するメンバを検索する。そして、それでも該当するメンバが検出されなかった場合には、さらに、そのプロトタイプに 格納されたオブジェクトの基となるオブジェクトのプロトタイプから適合するメンバを検索するというわけだ。


プロトタイプ・チェーン

 つまり、リスト12内の でwalkメソッドを呼び出すと、まず(1)hamインスタンス自身のメンバを、次に(2)Hamsterクラスのprototypeプロパティ内を、そ して、(3)Animalクラスのprototypeプロパティ内を、順に検索していくことになる。結果、Animal.prototypeで定義された walkメソッドを検出し、これを実行するというわけだ。

 このように、JavaScriptではプロトタイプにインスタンスを設定することで、インスタンス間の継承関係を形成することができる。もちろ ん、この継承関係はさらに多階層にすることも可能で、その場合にも順に階層をさかのぼって、最上位のObject.prototypeに行き当たるまでメ ンバの検索が行われることになる*4。そして、このようなプロトタイプの連なりを称して、「プロトタイプ・チェーン」と呼ぶ。

*4 第1回でも軽く触れたように、Objectオブジェクトはすべてのクラスの基本(基底オブジェクト)となるものだ。すべてのオブジェクトは暗黙的にObjectオブジェクトを継承し、Object.prototypeを参照している。

 さて、このプロトタイプ・チェーン。JavaScriptにおける継承機構であると述べたが、C#やVisual Basicのようなクラス・ベースのオブジェクト指向とは大きく異なるポイントがある。というのも、クラス・ベースのオブジェクト指向では継承関係が静的 に決まるのに対して、JavaScript(プロトタイプ・チェーン)では継承関係を自由に変更可能であるという点だ。

 例えば、以下の例を見てみよう。

var Animal = function() {}
Animal.prototype = {
  walk : function() {
   window.alert("トコトコ");
  }
};

var SuperAnimal = function() {}
SuperAnimal.prototype = {
  walk : function() {
   window.alert("ダーッ!");
  }
};

var Hamster = function() {};
Hamster.prototype = new Animal(); // Animalを関連付け
var ham1 = new Hamster();
ham1.walk(); // 「トコトコ」

Hamster.prototype = new SuperAnimal (); // SuperAnimalを関連付け
var ham2 = new Hamster();
ham2.walk(); // 「ダーッ!」
ham1.walk(); // ???
リスト13 プロトタイプ・チェーンを動的に切り替える例

 ここでは、Hamster.prototypeにAnimalオブジェクトを関連付けた状態でインスタンスham1を、その後、SuperAnimalオブジェクトに切り替えた状態でインスタンスham2を生成している。このため、それぞれ ではプロトタイプ・チェーンをたどって、Animal/SuperAnimalクラスのwalkメソッドを実行しているわけだ。

 ここまでは、ごく直感的に理解できる挙動だと思う。では、 の結果はどうなるだろう。インスタンスham1を生成したタイミングでは、Hamster.prototypeにはAnimalオブジェクトが関連付いていた。しかし、 の時点ではすでにHamster.prototypeにセットされているのはSuperAnimalオブジェクトである。とすると、 では現在のプロトタイプであるSuperAnimalオブジェクトのwalkメソッドが呼び出され、「ダーッ!」が出力されるのだろうか。

 しかし、結果は「トコトコ」となる。このことから、JavaScriptにおいて、いったん形成されたプロトタイプ・チェーンはその後の変更にかかわらず保存されることが理解できる。この点は間違えやすいポイントの1つでもあるので、注意していただきたい。

JavaScriptでオブジェクト指向プログラミング 2 / 4

プロトタイプ・ベースのオブジェクト指向

 ということで、インスタンス共通のメソッドを定義するには、インスタンスに対してではなく、(少なくとも)コンストラクタによって定義する必要が あることが分かった。しかし、「少なくとも」とただし書きが付いたことからも予想できるように、コンストラクタでメソッドを追加するのは好ましいことでは ない。

 というのも、クラス(コンストラクタ)はインスタンスを生成する都度、それぞれのインスタンスのためにメモリを確保する。リスト3の例であれば、Animalクラスに属するname、sex、toStringという3つのメンバを設定するわけである。

 ところが、toString「メソッド」については、すべてのインスタンスでそれぞれまったく同じ値を設定しているにすぎない。ここでは、 Animalクラスでメソッドが1つ登録されているだけなので、さほど問題にはならないかもしれないが、メソッドが10も20も登録されているクラスだと したらどうだろう。インスタンスごとに10も20ものメソッドを「無駄に」コピーしなければならなくなってしまう。これは当然、好ましい挙動ではない。

 そこで登場するのが、「プロトタイプ」という考え方であるのだ。

 JavaScriptにおけるすべてのオブジェクトは「prototype」という名前のプロパティを公開している。prototypeプロパ ティは、デフォルトで何らプロパティを持たない空のオブジェクト(プロトタイプ・オブジェクト)を参照しているが、適宜、必要に応じてメンバを追加するこ とが可能である。

 そして、ここで追加されたメンバは、そのままインスタンス化された先のオブジェクトに引き継がれる――もっといえば、prototypeプロパ ティに対して追加されたメンバは、そのクラス(コンストラクタ)を基に生成されたすべてのインスタンスから利用できるというわけだ。やや難しげないい方を するならば、

「関数オブジェクトをインスタンス化した場合、インスタンスは基となる関数オブジェクトに属するprototypeオブジェクトに対して、暗黙的な参照を持つことになる」

といい換えてもよいかもしれない。

 そろそろ分かりにくくなってきたという方のために、ここで具体的なコードを見てみることにしよう。以下は、リスト4でコンストラクタ経由により追加したtoStringメソッドを、prototypeオブジェクト経由で追加するように書き換えた例である。

var Animal = function(name, sex) {
  this.name = name;
  this.sex = sex;
}

Animal.prototype.toString = function() {
  window.alert(this.name + " " + this.sex);
};

var anim = new Animal("トクジロウ", "オス");
anim.toString(); // 「トクジロウ オス」
リスト6 リスト4をprototypeプロパティを使って書き換えたコード

 このようにAnimalクラスから生成されたインスタンス(ここではanim)は、Animal.prototypeプロパティによって参照されるオブジェクトを暗黙的に参照するようになる。

 「プロトタイプ・ベースのオブジェクト指向」というと、(特に「クラス・ベースのオブジェクト指向」に慣れた諸氏にとっては)なじみにくい概念に も感じられるかもしれない。しかし、要は「単にクラスという抽象化された設計図が存在しない」のがJavaScriptの世界なのである。

 JavaScriptの世界で存在するのは、常に実体化されたオブジェクトであり、新しいオブジェクトを作成するにも(クラスではなく)オブジェ クトをベースにしているというだけだ。そして、新しいオブジェクトを作成するための原型を表すのが、それぞれのオブジェクトに属するプロトタイプ・オブ ジェクト(prototypeプロパティ)なのである。クラスという抽象的な概念を間に差し挟まない分、より直感的な世界に思えてこないだろうか。

■プロトタイプ・オブジェクトを介する利点

 プロトタイプの概念が理解できたところで、話を戻そう。そもそも、コンストラクタでメソッドを追加するのは好ましくないという話から、プロトタイプが登場したわけであるが、プロトタイプを介することで何が変わるのだろうか。ポイントは2点だ。

(1)必要なメモリ量を節約できる

 繰り返しであるが、プロトタイプ・オブジェクトの内容はそれぞれのインスタンスから暗黙的に参照されるものだ。具体的には、アプリケーションからオブジェクトのメンバを参照する場合、内部的には以下のような順序で検索が行われている。


プロトタイプへの暗黙的な参照(1)

 最初にインスタンス側(ここではanim)にtoStringという名前のメンバが存在しないかを検索する。しかし、ここではインスタンス自身が toStringというメンバを持たないので、暗黙的な参照をたどってプロトタイプ・オブジェクトを取得し、そのtoStringメソッドを取得するので ある。

 つまり、インスタンス化に際して、プロトタイプ・オブジェクト配下のメンバが個々のオブジェクトにコピーされるわけではないので、それぞれのオブジェクトで消費するメモリを節約できるというわけだ。

 もっとも、ここでふと疑問がわき上がってくる。すべてのインスタンスが基となるオブジェクト(プロトタイプ)に対して暗黙的な参照を持つとする と、プロトタイプで提供されるメンバに対する変更は(いわゆるクラス変数やインスタンス変数のように)すべてのインスタンスで共有されてしまうのだろう か。

 具体的なコードで確認してみよう。

var Animal = function() {};

Animal.prototype.name = "サチ";
var a1 = new Animal();
var a2 = new Animal();

window.alert(a1.name + "|" + a2.name); // 「サチ|サチ」

a1.name = "トクジロウ";
window.alert(a1.name + "|" + a2.name); // 「トクジロウ|サチ」
リスト7 プロトタイプが提供するメンバをインスタンス側で変更する例

 nameプロパティはプロトタイプ・オブジェクト(Animal.prototype)で宣言されたプロパティであるが、結果を見ても分かるよう に、あるインスタンス(ここではa1)に対して施された変更は異なるインスタンス(ここではa2)には反映されていないことが確認できる。

 これはどうしたことだろう。結論からいってしまうと、プロトタイプに対する暗黙的な参照が利用されるのは、読み込みの場合だけであるのだ。書き込みはあくまでインスタンス自身に対して行われるため、プロトタイプに対して変更が影響することはない。

 内部的な挙動については、以下の図を見てみるとよい。


プロトタイプへの暗黙的な参照(2)

 初期状態では、インスタンスa1、a2ともにプロトタイプ・オブジェクトを参照しているわけであるが、a1.nameプロパティに対して新たな値 が設定されたところで、インスタンスa1の側ではプロトタイプ・オブジェクトのnameプロパティを参照する必要がなくなる。よって、インスタンス側で用 意されているnameプロパティが取得されるというわけだ*2。もちろん、この時点でインスタンスa2はnameプロパティを持たないので、そのまま暗黙の参照をたどってプロトタイプ・オブジェクトのnameプロパティを参照することになる。

*2 この状態を、インスタンスa1のnameプロパティがプロトタイプ・オブジェクトのnameプロパティを「隠ぺいする」といういい方をする場合もある。

 ちなみに、この考え方はdelete演算子の場合でも同様である。delete演算子は、オペランドとして指定された配列要素やプロパティ/メソッドを削除するための演算子だ。

 例えば、先ほどのリスト7の末尾に以下のようなコードを追加してみよう。

delete a1.name; // インスタンスa1のnameプロパティを削除
delete a2.name; // インスタンスa2のnameプロパティを削除

window.alert(a1.name + "|" + a2.name); // 「サチ|サチ」
リスト8 delete演算子によるメンバの削除

 インスタンスa1には独自の(プロトタイプ参照によって取得したのではない)nameプロパティが存在するので、delete演算子はこの値を削 除する。一方、インスタンスa2には独自のプロパティは存在しないので、delete演算子は何も行わないというわけだ(暗黙的な参照をたどって、プロト タイプ・オブジェクトが操作されることはない*3)。結果、それぞれのオブジェクトの状態は以下の図のようになる。


メンバの削除(delete演算子の挙動)

 インスタンスa1の側では、独自のプロパティが存在しなくなったので、再び暗黙的な参照をたどって、プロトタイプ・オブジェクトの値が有効になる というわけだ。繰り返しではあるが、インスタンス側でのメンバの追加/削除が、プロトタイプ・オブジェクトに対して影響を及ぼすことはないのである。

*3 もちろん、「delete Animal.prototype.name」のように記述すれば、プロトタイプ・オブジェクトのメンバを削除することも可能だ。もっとも、この場合は当 然のことながら、このプロトタイプを引き継いでいるすべてのインスタンスに影響を及ぼすので注意すること。

[参考]undefined値によるプロトタイプ・オブジェクトのメンバの無効化

 delete演算子ではなく、インスタンス側のプロパティにundefined(未定義)値を設定することで、疑似的にインスタンス側で(ほかのインスタンスに影響を及ぼすことなく)プロトタイプ・オブジェクトが提供するメンバを無効化することも可能だ。

 ただし、delete演算子がプロパティそのものを削除するのに対して、undefinedキーワードはあくまでプロパティそのものの存在はその ままに、値を未定義に設定するだけである点に注意してほしい(厳密にはこの場合、インスタンスに対して値がundefinedであるnameプロパティを 追加している)。つまり、for…inループでオブジェクト内のメンバを列挙した場合などには、undefinedキーワードで未定義となったプロパティ は依然として表示されることになる。

var Animal = function() {};

Animal.prototype.name = "サチ";
var a1 = new Animal();

a1.name = undefined;

for (key in a1) {
  window.alert(key + ":" + a1[key]);
} // 「name:undefined」
リスト9 undefinedキーワードでプロパティを未定義にする例

JavaScriptでオブジェクト指向プログラミング 1 / 4

第4回 JavaScriptでオブジェクト指向プログラミング

JavaScriptが、いま注目を浴びている。

 JavaScriptがこれだけの注目を浴びた理由の1つとして、Ajax技術の登場とも相まって、JavaScriptに対する確かな理解の必 要性が高まったという事情は否定できない。しかし、それだけでは説明できない急速な注目の理由として、もう1つ、JavaScriptという言語そのもの が持つユニークさが開発者の目を引いたという点は看過できないだろう。

 もっとも、このユニークさは同時に、多くの開発者が感じているJavaScriptに対する苦手意識と同義でもある。これまでVisual BasicやC#、Javaといった言語でオブジェクト指向構文になじんできた開発者にとって、JavaScriptのオブジェクト指向構文はいかにも奇 異なものに映るのだ。ようやくクラスという概念を理解した開発者が、JavaScriptという言語の背後にたびたび見え隠れする「プロトタイプ・ベース のオブジェクト指向」というキーワードに、そもそもの敬遠感を抱いているという事情もあるだろう。

 しかし、JavaScriptにおける「プロトタイプ・ベースのオブジェクト指向」は何ら目新しい概念ではない。ごくごく単純化していってしまえば、プロトタイプとは「より縛りの弱いクラス」のようなものと思っていただければよいだろう。

 連載最終回となる今回は、JavaScriptで本格的なプログラミングを行うのに欠かせない――そして、JavaScriptに対する苦手意識の根幹でもある(と思われる)「プロトタイプ・ベースのオブジェクト指向」構文について解説する。

 なお、本論に入るに当たって、1点のみ注意していただきたい点がある。第1回で も述べたように、JavaScriptでは厳密な意味でのクラスという概念は存在しない。しかし、本稿を読まれている読者の多くはクラス・ベースのオブ ジェクト指向構文になじんでいると思われることから、ここではJavaScriptにおいて「クラス的な役割を持つ存在」を、便宜上、「クラス」と呼ぶも のとする。ご了承いただきたい。

JavaScriptにおける“クラス”の定義

 JavaScriptにおけるオブジェクト指向構文について理解するには、抽象的な解説を重ねるよりも、まずは具体的なクラスを実際に作成してみた方が話が早いだろう。以下は、JavaScriptで中身を持たない、最も単純なクラスを定義した例だ。

var Animal = function() {};
リスト1 JavaScriptにおける最もシンプルなクラスの例

 変数Animalに対して、空の関数リテラルを代入しているだけのコードだ。「これがクラス?」と思われた方もいるかもしれないが、そのとおり、これがJavaScriptにおけるクラスなのである。れっきとしたクラスである証拠に、

var anim = new Animal();

と、クラス・ベースのオブジェクト指向構文でもおなじみのnew演算子を利用して、実際にインスタンス化を行ってみると、確かに正しく(エラーなども出ずに)実行できることが確認できる。ここではまず、

JavaScriptでは関数オブジェクトにクラスとしての役割を与えている

という点を覚えておこう。

■コンストラクタとプロパティ

 コンストラクタとは、オブジェクトを生成する際に自動的に呼び出される関数(メソッド)のこと。クラス・ベースのオブジェクト指向構文を少しでもかじったことのある方ならば、これはごく耳になじんだキーワードの1つであるはずだ。

 リスト1で記述したAnimal関数は、new演算子によって呼び出され、オブジェクトを生成するという意味で、厳密には「クラス」そのものというよりも「コンストラクタ」と呼ぶのがより正しいだろう。

 コードを見ても分かるように、JavaScriptにおいては、コンストラクタと(普通の)関数との間に本質的な違いはない。関数として呼び出す のか、それともnew演算子によって呼び出すかによって、関数の振る舞いが変わるわけである。もっとも、(構文規則ではないが)実際のコードで、通常の関 数とコンストラクタとが区別できないのは不便であるので、一般的にはコンストラクタ(クラス)名は大文字で始めるのが好ましい。

 さて、先ほどのリスト1では空のコンストラクタを定義したわけであるが、通常、コンストラクタの中では生成するオブジェクトを初期化するための コードを記述するのが一般的だ。具体的には、オブジェクト共通で利用するプロパティ(メンバ変数)などを定義するのがコンストラクタの役割なのである。

 以下は、リスト1で作成したAnimalクラスにname/sexプロパティを追加した例だ。

var Animal = function(name, sex) {
  this.name = name;
  this.sex = sex;
}
var anim = new Animal("トクジロウ", "オス");
window.alert(anim.name + ":" + anim.sex); // 「トクジロウ:オス」
リスト2 Animalクラスにname/sexプロパティを追加したコード

 ここで注目していただきたいのは、コンストラクタの中のthisキーワードだ。コンストラクタとして関数オブジェクトを呼び出した場合、thisキーワードは新たに生成されるオブジェクトを表すことになる。そして、

this.プロパティ名 = 値

のように記述することで、オブジェクトにプロパティを追加することができるというわけだ。実際、リスト2の例でも、コンストラクタで設定されたname/sexプロパティが、作成したオブジェクトから正しく参照できることが確認できるはずだ。

[注意]コンストラクタに戻り値は不要

 もう1点、コンストラクタを定義する場合のささやかな注意点がある。というのも、コンストラクタでは「戻り値を返す必要がない」という点だ。クラ ス・ベースのオブジェクト指向構文に慣れていればごく当たり前のポイントではあるが、プロトタイプ・ベースのオブジェクト指向構文でもこの点は同様である ので、あらためて注意しておきたい。コンストラクタ関数の役割は、あくまでこれから生成するオブジェクトの初期化を行うことであって、オブジェクトそのも のを返すことではない。

■メソッド - コンストラクタによる定義 -

 いまさらいうまでもなく、オブジェクトの構成は大きく「データ」と「手続き」とに分類できる。先ほどは、プロパティ(メンバ変数)を定義すること でオブジェクトの「データ」を定義したので、当然の流れとして、次は「手続き」の部分――「メソッド(メンバ関数)」を定義してみることにしよう。

 以下は、先ほどのAnimalクラスにtoStringという名前でメソッドを追加した例だ。

var Animal = function(name, sex) {
  this.name = name;
  this.sex = sex;
  this.toString = function() {
    window.alert(this.name + " " + this.sex);
  };
}
var anim = new Animal("トクジロウ", "オス");
anim.toString(); // 「トクジロウ オス」
リスト3 AnimalクラスにtoStringメソッドを追加した例

 JavaScriptには厳密な意味でのメソッドという概念はない。このリスト3を見ても分かるように、「値として関数オブジェクトが渡されたプ ロパティがメソッドと見なされる」わけだ。ここではtoStringという名前のプロパティに匿名関数を引き渡すことで、toStringというメソッド を追加したことになる。

 果たして、toStringメソッドを実行してみると、確かに「トクジロウ オス」というメッセージが返されることが確認できるはずだ。

■メソッド - インスタンスへの追加 -

 もっとも、JavaScriptではメソッドをあらかじめコンストラクタで定義できるばかりではない。インスタンス化されたオブジェクトに対しても、後からメンバを追加できるのがJavaScriptの特徴だ(これをJavaScriptの「動的性質」と呼ぶ)。

 そのJavaScriptの動的性質を利用することで、先ほどのリスト3は以下のように書き換えることも可能だ。

var Animal = function(name, sex) {
  this.name = name;
  this.sex = sex;
}
var anim = new Animal("トクジロウ", "オス");
anim.toString = function() {
  window.alert(this.name + " " + this.sex);
};
anim.toString(); // 「トクジロウ オス」
リスト4 インスタンスに対してメソッドを追加した例

 先ほど同様、toStringメソッドが正しく動作していることが確認できるはずだ。ただし、インスタンスに対して動的にメソッドを追加した場合には、注意も必要だ。

 以下に、Animalクラスに対するインスタンスを2つ登場させた例を見てみよう。

var Animal = function(name, sex) {
  this.name = name;
  this.sex = sex;
}
var anim = new Animal("トクジロウ", "オス");
anim.toString = function() {
  window.alert(this.name + " " + this.sex);
};
anim.toString(); // 「トクジロウ オス」

var anim2 = new Animal("リンリン", "メス");
anim2.toString(); // エラー発生
リスト5 Animalクラスに対してインスタンスを複数生成した例

 インスタンスanim2からtoStringメソッドを呼び出そうとした時点で、エラーが発生することが確認できる。Internet Explorerでは、恐らく「オブジェクトでサポートされていないプロパティまたはメソッドです。」というエラーメッセージが出力されるはずだ。

 このことから、インスタンスへのメソッドの追加はあくまでインスタンス(ここではanim)への追加であって、「大本であるクラスへの追加ではな い」ことが理解できる。つまり、クラス・ベースのオブジェクト指向言語においては、あるクラスを基に作成されたインスタンスは必ず同じメンバを持つはずで あるが、JavaScriptでは同一の“クラス”を基に作成されたインスタンスでも異なるメンバを持つ可能性がある、ということなのである。これが、先 ほどプロトタイプ・ベースのオブジェクト指向が、クラス・ベースのそれよりも「縛りが弱い」と述べた理由でもある*1

*1 ちなみに、ここではメンバを追加する例について述べたが、インスタンス、あるいはコンストラクタ経由で追加したメンバはdelete演算子(後述)で削除することも可能だ。

 いずれにせよ、ここでは、

インスタンスに追加したメンバは、
(オブジェクト共通ではなく)そのインスタンスのみで有効である

という点を押さえておこう。