2012/11/22

【22日目】prototype vs Proxy


この記事は、@astronaughtsさんが主催の11月1日~30日まで毎日誰かがTitanium Mobileに関する記事を書くイベント「Titanium mobile “early” Advent Calendar 2012」向けにまとめたものです。



prototypeによる継承


prototypeといえば、JavaScriptで「クラス」継承のようなことをするための仕組みとして有名です。CommonJS のモジュールとしてまとめると、以下のようにして SuperClass からSubClass を作ります。


SuperClass.js

(function _SuperClassModule() {
    function _f() {
        Ti.API.debug("superclass constructed!");
        // init properties here using this keyword
        /* example
        this.no = 1;
        */
   }
   _f.prototype.classname = "superclass"; // custom defined property for debug
    // init methods here using _f.prototype

    /* example
    _f.prototype.getNo = function() { return this.no;};

    */

    // export
    module.exports = _f;
}).call(this);

SubClass.js

(function _SubClassModule() {

    var superclass = require("ui/common/SuperClass");
    function _f() {
        _f.prototype._superconstructor.apply(this, arguments);
        Ti.API.debug("subclass constructed!");
        // init properties here using this keyword
    }
    _f.prototype = Object.create(superclass.prototype);

    _f.prototype._superconstructor = _f.prototype.constructor;

    _f.prototype.constructor = _f; // for subclass of this subclass

    _f.prototype.classname = "subclass"; // custom defined property for debug
    // init methods here

    // export
    module.exports = _f;
}).call(this);

prototype により、プロパティチェーンをたどって、オブジェクトのメソッドやプロパティにアクセス可能にして、あたかも関数オブジェクトがクラスであるかのように振る舞うように見せかける仕組みです。ちょっと工夫している点は、 
`_f.prototype._superconstructor`

という独自のプロパティを prototype に定義して、コンストラクタ役の関数 _f で、スパークラスのメソッドの名前を指定しなくても良くしている点です。 また、 
_f.prototype.constructor
に対して、new _f() を使う流儀もありますが、直接 _f を設定しているのは、関数オブジェクトを増やさないための工夫です。

SubClassのインスタンスを生成するには、

var SubClass = require("SubClass"); 
var sub = new SubClass();

のように、require でモジュールを読み込んだ上で、new を用いて生成します。これで、SuperClass を継承した関数オブジェクトのインスタンスが生成されます。 通常の JavaScriptのオブジェクトであれば、prototype を使ったこの方法が一般的だと思われます。

Proxyオブジェクトの継承(擬)


Ti.UI, Ti.UI.Viewなど、Titanium の組み込みモジュールやオブジェクトは、純粋な JavaScriptのオブジェクトではなく、Ti.Proxy を継承したKroll 由来の独自のオブジェクトです。 そのため、これらを継承して新しいオブジェクトを作るには、上記の prototype を使った手法が使えません。そもそも protptypeという独特なプロパティを持たないからです。 そこで、次のようにして、継承のまねごとをしてみます。

`CustomView.js`
(function _CustomViewModule(){ 
    function testFunc(factor) { 
        Ti.API.debug("CustomView testFunc"); 
        return this.testProp * factor; 
    
    function _f(opt) { 
        var self = Ti.UI.createView(opt); // create View instance as example Ti.Proxy 
        // init properties and methods here using variable self 
        self.testProp = 1; 
        self.testFunc = testFunc; 
        Ti.API.debug("castomview constructed!");             return self; // return instance of Ti.Proxy 
    
    module.exports = _f;
}).call(this);

 `SubCustomView.js ` 


(function _SubCustomViewModule(){ 
    var CustomView = require("ui/common/CustomView"); 
    function testFunc(factor) { 
        Ti.API.debug("SubCustomView testFunc"); 
        return this.testProp * factor * factor; 
    
    function _f() { 
        var self = _f._superConstructor.apply(this, arguments); 
        // init properties and methods here using variable self 
        self.testFunc = testFunc; 
        Ti.API.debug("subcastomview constructed!"); 
        return self; 
    
    _f._superConstructor = CustomView; 
    module.exports = _f;
}).call(this);

Proxyオブジェクトは、Java や Objective-C のネイティブオブジェクトを JavaScript から使えるようにするためのオブジェクトです。純粋な JavaScript のオブジェクトではないとはいえ、JavaScript らしい性質は持っています。Proxy オブジェクトはユーザー定義のプロパティをいつでも追加できる仕組みになっています。この性質を利用して、独自のプロパティ(関数オブジェクトを設定すれば、メソッド)を自由に設定できます。

ただ一点うまくいかないのは、Proxy オブジェクトがあらかじめ持っているメソッドの変更や変数への代入ができない点です。これは、Proxy オブジェクトの定義済みメソッドがJavaScript の関数オブジェクトではないからだと考えられます。

Proxy に対して、独自に関数オブジェクトをプロパティとして追加し、呼び出すことには問題ありません(上のコードでは意味のない関数ですが View オブジェクトに testFunc を追加したり、オーバーライドしたりしています)。

prototype による継承の場合と違って、Proxy の継承の場合、コンストラクタ役の関数が Proxy オブジェクトを返さなければなりません。

return self;

を忘れないように(prototype による継承の場合、return が必要ないのは、値を返さないJavaScript 関数は、関数オブジェクト自体を返すからです)。

上記の例では、

var SubCustomView = require("SubCustomView");
var sub = SubCustomView();


のようにしてオブジェクトを生成します。new を使わなくてよい点に注意してください(new しても、表面上の動作に違いはありませんが、コンストラクタ役の関数オブジェクトが裏で生成されれる点が異なります)。

生成されたオブジェクトは Ti.UI.View オブジェクトのインスタンスにすぎないので、View や Window に add したり、通常の場合と同じ扱いができます。

View の画面上の配置、View のコントロールとしての表現、ビジネスロジックなどなど、それぞれ階層分けして組み立てるのに、この方法が使えるかもしれません。

(しかし、よく観察してみると、View に対するプロパティの組み立てを、いくつかに分けて実施するということにすぎないこともわかります。Alloy なども、煎じ詰めれば、同じことやっていることになりますね。)




Proxyオブジェクトの継承(擬) その2


前の例では、メドッドをオーバーライドする際、対応するプロパティを書き変えてしまっているので、もはや親クラスのメソッドへアクセスすることができません。 これに対する、方法もありますが、説明が長くないそうなので、GitHub にコードをあげておきました。 スーパークラスのチェーンを作って、それに基づいて、メソッドを呼び出せる仕組みになっています。興味のある方はご覧ください。

TitaiumProxyInheritance

参考文献

ECMA–236



おまけ




0 件のコメント:

コメントを投稿