2010-11-29
■ [ruby][event] Ruby Advent Calendar 2010の参加者募集中です
あのRuby Advent Calendar(jp: 2009)が帰ってきた!
Advent Calendarは、12月のクリスマスまでを数えるカレンダーで、1日に1つずつ穴を開けていく感じのものです。ものによっては中にお菓子が入っていたりします。
それになぞらえて、1日に1つずつRubyに関するTipsを書いていくのがRuby Advent Calendarです。参加方法は、ATNDで登録して、担当日に自分のブログに記事を投稿するだけです。3行でもいいので、ぜひ書いてみてください。
英語トラック
今年はJPerl Advent Calendar 2010 - English Trackにならって、英語トラック(http://atnd.org/events/10439 )もあります。 25人集まるのか分からないですけど、去年の各種Advent Calendarを見ると25人いかなかったのもあるので、それでもいいかなと思って立ててみました。
The English / Japanese barrier hides the existence of rabbit, qwik and tdiary you should know about.
[Twitter - yuguiより引用]
というのはyuguiさんのpostですが、日本から英語圏への発信がもっと上手くいくようになればいいなと思っています(最近だと rubyflowに投稿するという手があったりしますけど)。■ [javascript] JavaScriptのプロトタイプベースオブジェクト指向をRubyと比較する
JavaScriptはプロトタイプベースのオブジェクト指向言語で、RubyやJavaにあるような「クラス」というものがありません。 しかし、プロトタイプをうまく使うことで、クラスベースのオブジェクト指向に近いことができます。
というわけで、RubyとJavaScriptを比較してみました。「もしJavaScriptがクラスベースオブジェクト指向言語だったら」:
Ruby | JavaScript | ||
---|---|---|---|
名称 | クラス | コンストラクタ | |
定義 | class Foo | var Foo = function(){... | |
インスタンス生成 | Foo.new() | new Foo() | |
初期化方法 | def initialize() | function Foo() | |
自分のクラスの取得 | foo.class (=> Foo) | foo.constructor (=> Foo) | |
インスタンスかどうか | foo.instance_of?(Foo) (=> true) | foo instanceof Foo (=> true) | |
メソッド | |||
クラスメソッド | def self.do_something() | Foo.doSomething = function(){... | |
Foo.do_something() | Foo.doSomething(); | ||
インスタンスメソッド | def something() | Foo.prototype.something = function(){... | |
foo.something() | foo.something(); | ||
特異メソッド | def foo.do_special() | foo.do_special = function(){... | |
foo.do_special() | foo.do_special(); | ||
継承 | |||
サブクラスの定義 | class Apple < Fruit | Apple = function(){}; Apple.prototype = new Fruit(); |
以下解説です。JS記事久々だし嘘があるかも知れません…。
JSのコンストラクタはクラスに近い
前述のようにJavaScriptにはクラスがありませんが、 コンストラクタ(オブジェクトの生成・初期化に使われる関数)がクラスに近い役割を持ちます。
クラスメソッドやクラス定数のようにクラスに属するものは、コンストラクタ自身に 定義されます(例:Foo.CONST = 1)。JavaScriptでは関数もオブジェクトなので、任意のプロパティを追加することができることを利用しています。
インスタンスメソッドやインスタンスプロパティのように、インスタンスに属するものは、Foo.prototypeにまとめて定義されています。つまり、Foo.prototypeはFooのインスタンスの原型である…という意味で、prototypeという名前が付けられているのだと思います。
プロトタイプはメソッド呼出しのときに使われる
ここからは、fooに対するFoo.prototypeのことを「fooのプロトタイプ」と書きます(日本語は便利ですね)。文字列"a"のプロトタイプはString.prototypeです。配列[1,2,3]のプロトタイプはArray.prototypeです。関数function(){}のプロトタイプはFunction.prototypeです。オブジェクト{a: 1}のプロトタイプはObject.prototypeです。 *1
foo.method()という呼出しがあったとき、まずfoo自身がmethodという名前の関数を持っているかどうかがチェックされます。これはRubyでいう特異メソッドに相当します。で、存在しなければ、fooのプロトタイプのmethodが使われます。つまりFoo.prototype.methodですね。このように、メソッド呼出し時にプロトタイプをチェックすることで、インスタンスメソッドの呼出しが実現されています。
さて、ここまでは良いでしょうか?(オライリーのJavaScript 第5版 第9章を読むと、もっと丁寧な説明があります)
4873113296
単一継承を実現する
ここから、JavaScriptで「継承」を表現する方法に入ります。
foo.toString()というメソッド呼出しを考えます。fooがtoStringという関数を持たない場合、fooのプロトタイプのtoString(つまりFoo.prototype.toString)が探されます。ではこれも存在しなかった場合はどうなるでしょうか?このような時は、Foo.prototypeのプロトタイプから探されることになっています。インスタンスメソッド群をFoo.protoype = {...}のような形式で定義していたとすれば、Foo.protoypeはObjectのインスタンスですから、Object.protoypeからtoStringを探すことになります(そしてObject.protoypeにはtoStringが存在するので、それが呼ばれます)。
toStringの探索順序をまとめると、foo→Foo.protoype→Object.protoypeのようになっています。これは、「FooがObjectのインスタンスメソッドを継承している」という風に見ることができます。 普通に「function Foo()」のように宣言した場合、Foo.prototypeにはObjectの直接のインスタンスが設定されることになっているので、「デフォルトの親クラスはObjectである」と言えます。
これを応用して、コンストラクタのprotoype属性を適切に設定してやることで、ユーザクラス間の継承も実現できます。例えばFruitのサブクラスAppleを定義したいときは、Apple.protoype = new Fruit(); とすれば、Apple.protoypeのプロトタイプ == Fruit.protoypeとなるので、インスタンスメソッドの探索順序がApple.protoype→Fruit.protoype→Object.prototypeのようになります。 *2
現実には…
これでインスタンスメソッドの継承は実現できましたが、これだけだとコンストラクタ内の処理は継承されないので、明示的に
function Apple(price){ Fruit.call(this, price); }
のように呼び出すか、
function Apple(price){ this.superclass(price); } Apple.prototype.superclass = Fruit;
などと*3してやる必要があります。
こんな感じで、「class Apple < Fruit」ほど簡単にはクラスベースOOP的な継承を行うことはできないようです。 フレームワークによっては、Class.create(Prototype.js)や$.extend(jQuery) など、継承を助ける機能が用意されています。
*1 ちなみにあるインスタンスのプロトタイプを取得する書き方はfoo.__proto__とかObject.getPropertyOf(foo)とかがありますが、いずれも環境依存です。通常はfoo.constructor.prototypeで取得できますが、これはfooの生成後にconstructorとかそのprototypeを変更したりすると実際に使われるプロトタイプとずれてしまいます
*2 環境依存かつobsoleteですが、環境によってはより直接的に「Apple.prototype.__proto__ = Fruit.prototype;」と書けるようです
*3 オライリー本より。見た目的にはいいが、superclassがインスタンスプロパティってのはちょっと気持ち悪いよね