2007/12/17
javascriptを初めて学ぶ人についてのおさらい。その2
前回のエントリが700users突入しました。ありがとうございます。参考になれば幸いです。
ということで、その2になります。
前回書いた通り、C/Javaについてはある程度の知識がある人なので、クラスなどのオブジェクト指向はちゃんと理解されているようですが、プロトタイプ指向は初めて学ぶようです。
javascript(ECMAScript)のプロトタイプは他のプロトタイプ指向言語とはひと味違う動作をするので、その点も含めておさらい
プロトタイプとはなんですか?プロトタイプとは継承パターンの一つでしかないです
プロトタイプは単なる継承パターンであり、単一の方向への継承しか行わない点についてはクラスベースと同じです。
var Hoge = function (){}; Hoge.prototype.methodA = function (){ return "this is methodA"; }; var Foo = function (){}; Foo.prototype = new Hoge; var foo = new Foo; alert(foo.methodA()); // this is methodA alert(foo instanceof Foo && foo instanceof Hoge); // true
また、上の記述は次のように書く事もできます
var Hoge = function (){ this.methodA = function (){ return 'this is methodA'; }; this.methodB = function (){ return 'this is methodB'; }; }; var Foo = function (){}; Foo.prototype = new Hoge; var foo = new Foo; alert(foo.methodA()); // this is methodA alert(foo.methodB()); // this is methodB alert(foo instanceof Hoge && foo instanceof Foo); // true
しかし、次のような記述を指定してしまうと、上記の動作とは違ってきます。
var Hoge = function (){ this.methodA = function (){ return 'this is methodA'; }; }; Hoge.prototype.methodB = function (){ return 'this is methodB'; }; var Foo = function (){ // apply Hoge.apply(this, arguments); }; var foo = new Foo; alert(foo.methodA()); // this is methodA alert(foo.methodB); // undefined alert(foo instanceof Hoge && foo instanceof Foo); // false // Fooと同じ var Bar = function (){}; Hoge.apply(Bar.prototype); var bar = new Bar; alert(bar.methodA()); // this is methodA alert(bar.methodB); // undefined alert(bar instanceof Hoge && bar instanceof Bar); // false
この違いが生まれることについては、後記
インスタンスにメソッドをつけてprototypeすることも可能です。特異メソッドのように
rubyの特異メソッド(以下)では、インスタンスにメソッドを付加することができます。
class Hoge def hello 'hello' end end foo = Hoge.new p foo.hello # hello def Hoge.world 'world' end p foo.respond_to?('world') #false def foo.world2 'world2' end p foo.respond_to?('world2') #true p foo.world2 # world2
特異メソッドについては、javascriptでも実装可能です。また、特異メソッドをつけたインスタンスをprototypeすることが可能となっています。
var Hoge = function (){}; Hoge.prototype.hello = function (){ return 'hello'; }; var foo = new Hoge; alert(foo.hello()); // hello foo.world = function (){ return 'world'; }; alert(foo.world()); // world var Bar = function (){}; Bar.prototype = foo; var bar = new Bar; alert(bar.hello()); // hello alert(bar.world()); // world
Javascriptのプロトタイプは、インスタンスベースにあらず。
上のインスタンスメソッドによる動きをサポートしている(?)おかげで、次のような記述が可能となり、プロトタイプ言語ならではな、ダイナミックなプログラミングが可能となります。
var Echo = { hello: function (){ return 'hello'; } }; var Hoge = function (){}; Hoge.prototype = Echo; var hoge = new Hoge; alert(hoge.hello()); // hello Echo.world = function (){ return 'world'; }; alert(hoge.world()); // world
しかしながら、javascriptではクラスベースのインスタンスを生成するため、以下のように書いてしまう事で動作が不明になり、混乱してしまいます。
var Echo = function (){}; Echo.prototype = { hello: function (){ return 'hello2'; } }; var Hoge = function (){}; Hoge.prototype = new Echo; var hoge = new Hoge; alert(hoge.hello()); // hello2 Echo.world = function (){ return 'worl2'; }; alert(hoge.world); // undefined Echo.prototype.world = function (){ return 'world3'; }; alert(hoge.world()); // world3
上記undefinedになる原因は、Echoクラスに対してworldメソッドを追加したものの、既に生成済みインスタンスがEchoのインスタンスと違っているため、このような動きになります
javascriptのプロトタイプはインスタンスについてのプロトタイプではなく、prototype宣言されたものについてプロトタイプされる言語といえそうです。
このあたりの動きが、javascriptのプロトタイプが嫌われる原因なのかもしれません。(慣れない人は本当に慣れないですよね…)
また、prototypeを自由に切り替えれる点においてもなかなか慣れないみたいです(あまり有効な使い方は無いんですけどね)
var Hoge = function (){}; Hoge.prototype.a = function (){return 'a'}; var Foo = function (){}; Foo.prototype.b = function (){return 'b'}; var Bar = function (){}; Bar.prototype = new Hoge; var bar = new Bar; alert(bar.a); // a alert(bar.b); // undefined Bar.prototype = new Foo; var bar = new Bar; alert(bar.a); // undefined alert(bar.b); // b
同一名プロパティ(slot)はプロトタイプできないのです
prototype言語のよいところは、未実装のメソッドについては、自動的に親のメソッドを探しに行ってくれる点にあると言えます。
var Hoge = function (){}; Hoge.prototype.methodA = function (){return 'a'}; var Foo = function (){}; Foo.prototype = new Hoge; Foo.prototype.methodB = function (){return 'b'}; var Bar = function (){}; Bar.prototype = new Foo; Foo.prototype.methodC = function (){return 'c'}; var bar = new Bar; alert(bar.methodA()); // 'a' alert(bar.methodB()); // 'b' alert(bar.methodC()); // 'c'
しかしjavascriptでは、prototypeを上書きしてしまうので、同一プロトタイプでは親の実装を呼び出す事ができません。
var Hoge = function (){}; Hoge.prototype.methodA = function (){ return 'this is Hoge#methodA'; }; var Foo = function (){ this.methodA = function (){ return 'this is Foo#methodA'; }; }; Foo.prototype = new Hoge; var foo = new Foo; alert(foo.methodA()); // this is Foo#methodA alert(Foo.prototype.methodA()); // this is Hoge#methodA
ただし、prototypeを巡って呼び出す事もできます。
jsは3分の1の純情なインスタンスベース、クラスベース、プロトタイプベース。ならcloneがおすすめ。
ということで、複数のインスタンスを生成できてしまうjavascriptでは混乱を多く招いてしまいます。常に単一方向への継承をするように、以前書いたプロトタイプ継承のcloneを行うとすんなり理解できるのではないでしょうか
Object.prototype.clone = function (){ var f = function (){}; f.prototype = this; return new f; }; var hoge = {}.clone(); hoge.a = function (){return 'a'}; var foo = hoge.clone(); foo.b = function (){return 'b'}; var bar = foo.clone(); alert(bar.a()); // a alert(bar.b()); // b hoge.c = function (){return 'c'}; alert(foo.c()); // c alert(bar.c()); // c
継承ってなんですか?プロパティのコピーですか?
プロトタイプ指向言語においては、全てがインスタンスであり、インスタンスをcloneすることで、親オブジェクトの変更が子オブジェクトに対しても反映されます。
以下は、ioのコードになります
Echo := Object clone do( hello := method( "hello" ) ) hoge := Echo clone hoge hello println // hello Echo world := method( "world" ) hoge world println // world
また、ioによく似た(というか、smalltalkによく似ている) Slate では次のように記述する事ができます(Slateもまたprototype指向の言語の一つです)
Slate 1> lobby addSlot: #Echo valued: Cloneable clone. lobby Slate 2> _@Echo hello [inform: 'hello']. [hello] Slate 3> lobby addSlot: #hoge valued: Echo clone. lobby Slate 4> hoge hello print. hello NilNil Slate 5> _@Echo world [inform: 'world']. [world] Slate 6> hoge world print. The method #world was not found for the following arguments: {("Cloneable" )} Nil Slate 7> Echo removeSlot: #world. ("Cloneable" ) Slate 8> _@(Echo traits) world [inform: 'world']. [world] Slate 9> hoge world print. world NilNil Slate 10> quit.
ioとslateは非常によく似ていますねえ。(slateはioと違いtraitsの概念がある分面倒になりますが)
で、jsのコード(using: prototype.js)
既に、記述のとおり、インスタンスに対してprototypeするのではなく、Class.createした関数のprototypeについて拡張を行います
var Echo = Class.create({ hello: function (){ return 'hello'; } }); var hoge = new Echo; alert(hoge.hello()); // hello Object.extend(Echo.prototype, { world: function (){ return 'world'; } }); alert(hoge.world()); // world
ついでに、継承についてはprototype.jsのおかげで豊富に実装可能ですが、instanceofの結果は正しくない(?)みたいです。
var Hoge = Class.create({ a: function (){return 'a'} }); var Foo = Class.create({ b: function (){return 'b'} }); var Bar = Class.create({ c: function (){return 'c'} }); var Baz = function (){}; Object.extend(Baz.prototype, new Hoge); Object.extend(Baz.prototype, new Foo); Object.extend(Baz.prototype, new Bar); var baz = new Baz; alert(baz.a()); // a alert(baz.b()); // b alert(baz.c()); // c alert(baz instanceof Hoge && baz instanceof Foo && baz instanceof Bar); // false
prototype.jsの1.6.0のコードでもObjext.extendは次のようになっているため、確かに、プロパティのコピーが行われているだけみたいです。(これも注意点?)
Object.extend = function(destination, source) { for (var property in source) destination[property] = source[property]; return destination; };
jsが持っているデフォルトオブジェクトにprototypeしよう
ということで、jsのデフォルトオブジェクトです。これをプロトタイプで拡張することができるので色々と楽しむ事ができます。が、度が過ぎる(Object.prototypeとかに設定すると)とhasOwnpropertyで何回も回ることになるのでほどほどに。
- Object.prototype
- String.prototype
- Number.prototype
- Boolean.prototype
- Date.prototype
- RegExp.prototype
- Function.prototype
- Array.prototype
- Error.prototype
ref - Core JavaScript 1.5 Reference:Global Objects - MDC
おさらい。その2
ということで、長々と書きましたが、その2でした。
継承パターンとしてのプロトタイプと、その記述(実装)による動きの違いを身につけてもらえれば、結構便利にプロトタイププログラミングできるのではないでしょうか。
どちらかというと、クラス指向(?)の実装と比べ、プロトタイプ指向は馴染めない実装のようですが、仕組みが簡単なので理解してもらうと楽しいです。(学ぶことが多すぎるのが難点ですけどね)
その3へつづく(?)
1 Trackback
[JavaScript][prototype]メソッドがなければprototypeをさかのぼる
ハタさんのブログ : javascriptを初めて学ぶ人についてのおさらい。その2を見ながら学習 var Echo = { hello: function (){ return ’hello’; } }; var Hoge = function (){}; Hoge.prototype = Echo; var hoge = new Hoge; alert(hoge.hello()); // hello Echo.world = fu
Track from Your Website
http://blog.xole.net/trackback/tb.php?id=645

Comment
No Comments