Hatena::ブログ(Diary)

toweroflの日記 RSSフィード Twitter

2012-01-05

俺流コーディングスタイルにダメ出し

昨年末に書いたこれですがその後Appceleratorが出してきたチュートリアルによって見事にダメ出しされてるので今日はその発表。
CommonJS Modules in Titanium
Mobile Best Practices

そのチュートリアルはid:donayamaさんが訳してくださってます。いつも本当にありがとうございます。
CommonJS Modules in Titanium(訳)
Titanium Mobile Best Practices(訳)

ダメ出しその1

JavaScriptモジュールがロードされた時、requireによって返されるオブジェクトTitaniumによってキャッシュされ、複数回ファイルの中身を評価する事なく、利用者に提供されます。

http://d.hatena.ne.jp/donayama/20111230/commonjs_modules_in_titanium

へっ、

// myModule.js
Ti.API.info('My module load');
exports.myModule = {};
// app.js
require('myModule');
require('myModule');
require('myModule');
require('myModule');
require('myModule');
//SDK 1.7.5
[INFO] My module load
[INFO] My module load
[INFO] My module load
[INFO] My module load
[INFO] My module load

あれ、そうなってない?

//SDK 1.8.0
[INFO] My module load

あ、1.8だとそうなってる。
件のエントリではこういう書き方をしています。

//app.js
var App = {};
(function() {
        App.helpers = {};
        App.helpers.util = require('App/helpers/util');
        App.helpers.style = require('App/helpers/style');
        App.models = {};
        App.models.Person = require('App/models/Person');
        App.views = {};
        App.views.win = require('App/views/win');

しかし、SDK1.8.x以降は重複して読み込まれないようなのでわざわざグローバル変数に入れて共有する必要はなく、むしろ使用される直前まで読み込まない方が良いので、

//app.js
var App = {};
(function() {
        //...

となります。上のコードは記憶から抹消してください。

ダメ出しその2

アプリケーション内のすべてのモジュールにわたって共有されるグローバル変数は存在してはいけません

http://d.hatena.ne.jp/donayama/20111230/commonjs_modules_in_titanium

JavaScriptではアプリケーション全体で使用できるグローバルオブジェクトが存在し、グローバルな名前空間で定義された変数はグローバルオブジェクトのプロパティとなります。
Titanium Mobileにおいてグローバルオブジェクトとはすなわちapp.jsで定義されたものがこれにあたります。

http://d.hatena.ne.jp/donayama/20120102/bestpractices

へっ、
上記の場合Appが見事に該当しますありがとうございました。もちろんこれもダメ。

//app.js
var App = {};
App.foo = 'Hello'

この後どこでモジュールをrequireしてもAppが参照できるだけでなくいつでも変更できてしまいます。
そこでapp.jsでは即時関数を使ってグローバルオブジェクトを汚染しないことが求められるようです。

//app.js
(function() {
    var App = {};
    var module = require('myModule');//これだとmyModuleからAppは参照できない。
    
})();

当然このままだとAppはグローバル変数の役割は果たしてくれません。じゃあ複数のモジュール間で変数を共有したい場合はどうすればいいのかと考えると基本的にはモジュールに定義された関数を呼ぶタイミングで共有したいオブジェクトを引数として渡すことになるかと思います。

//myModule.js
esports.someFunction = function(App){
        alert('Hello ' + App.foo);//->Hello Nobita
        App.foo = 'Doraemon';//ドラえもんに変更
};
//app.js
(function() {
        var App = {};
        App.foo = 'Nobita'//のび太の状態で
        var module = require('myModule');//モジュールを呼び出して
        module.someFunction(App);//モジュールの関数にAppを渡すと
        alert('Hello ' + App.foo);//->Hello Doraemon App.fooはドラえもんになって帰ってきた
})();

また、CommonJSのモジュールも参照渡しされるということなので、共通のモジュールを読み込むことでグローバル変数のように使うことも可能です。

//global.js
        exports.vars = {
                nobita: 'Nobi Nobita',
                gian: 'Goda Takeshi',
        }
//foo.js
        vars = require('global').vars;
        //...
//bar.js
        vars = require('global').vars;
        //...同じモジュールを読み込んだfooとbarの間で変数を共有できる。

ただしこのような使い方が推奨されるかはわかりませんが。

ダメ出しその3

組み込みオブジェクトに拡張するための一般的な方法は、Parasitic Inheritance(他のオブジェクトへコピーし、メソッドやプロパティを追加する)ですが、標準的なJavaScriptでは妥当なプラクティスである一方、Ti.UI.createViewなどで返されるオブジェクトに代表されるプロキシオブジェクトを使用する際には予期しない動作をする可能性があります。

http://d.hatena.ne.jp/donayama/20120102/bestpractices

おっしゃりたいことはよく解ります。「俺流〜」のウィンドウ間のデータの引渡しで

App.createBWindow = function() {
        var window = Ti.UI.createWindow();
        //...
        var refresh = function(person) {//windowの内容を更新するメソッド
                 window.title = person.name;
                 //...
        };
        window.refresh = refresh//windowオブジェクトにひもづける。
        return window;
};

と書いているのですが、ここでTiのviewオブジェクトであるwindowにrefreshという関数を追加してます。こうしたやり方は危険を伴うということで、Javascriptでのいわゆるクラス的なアプローチを推奨しています。

//Window.js
Window = function() {
        this.window = Ti.UI.createWindow();
        //...
        this.refresh = function(person) {//windowの内容を更新するメソッド
                 this.window.title = person.name;
                 //...
        };
};
exports.Window = Window;
//another.js
Window = require('Window').Window;
win = new Window();//インスタンスを生成
win.refresh(person);//ウィンドウの内容を更新。winに対する関数であることに注意
win.window.open();//ウィンドウをオープン。win.windowに対する操作であることに注意

個人的には上にも書いているようにどうしてもJavascriptのインスタンスとTiのオブジェクトの二重構造になることによる混乱がおきやすく敬遠してたのですが、、、。
クラス的な書き方はCoffeeScriptを使うととても簡単に書けるのでまだの方はこの際導入をお勧めしときます。

ということで、またもコードのサンプルを公開。ただ、今日のエントリの内容を含めて同じように後で訂正を書くはめになるかもしれませんのでご注意を。
なによりAppcelerator自身がアプリの土台を生成する機能を提供するという予定だったと思うので、皆さんにおかれましてはそちらを待たれる方が良いかと思います。

あと過去のエントリを焼却処分する良い方法をご存じないでしょうか?。

弟子弟子 2012/01/12 20:27 すばらしい、ありがとうございます。
コードまでご公開いただくとは。師と仰がせていただきます。

towerofltowerofl 2012/01/12 22:02 弟子だなんて謙遜しておいて、すっごいかっこいいアプリをさらりと作ってたりするんですよね。きっとそうに違いない。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証