ログ日記

作業ログと日記とメモ

JavaScriptでデザインパターンの代わり

よくJavaScriptデザインパターンとか書いてある記事を見かけるけど、無駄に複雑になるだけで必要ないと思う。
オブジェクトの一部の関数を入れ替えたければ普通に上書きすればいいんだし。


TemplateMethodパターンの場合

MyClass.prototype.myfunc = function(){
    this.preMyfunc();
    ....
    ....
    ....
    this.postMyfunc();
    ....
}

MyClass.prototype.preMyfunc = function(){
    ....
}

MyClass.prototype.postMyfunc = function(){
    ....
}
window.onload = function(){
    // このスクリプトを読み込むページのみ処理を変えたい場合はプロトタイプを上書き
    MyClass.prototype.preMyfunc = function(){
        ....
    }

    var myClass = new MyClass();
    ....
}

こんな感じで処理を変えたくなった時点でpreMyfunc等を切り出して上書きすればいいんじゃなかろうか。
書き換えは一度まで(上のように共通の処理を行う定義があって、呼び出し用のファイルから上書きする)などを決めておかないと大変なことになるだろうけど。


これとは関係ないが、別ファイルのオブジェクトを利用しようとするとたまに定義されてないというエラーが出る。
書き方が悪いんだろうか。onloadの前に初期化処理が実行されるようなコードを書いていたのかも。

bindとかcallとか

bindとcallって何が違うんだろうと思ったら、関数を返すか関数を実行するかが違うんだね。
で、自分のコードを見てみると

Webmail.prototype.viewList = function(text, args){
    var cols = document.getElementsByClassName('subject');
    var webmail = this;

    cols.each(function(col){
        var row = col.parentNode;
        col.onclick = function(){
            webmail.rollout.call(row);
            webmail.showMessage(row.id);
        }
    });
}

のようなコードがあった。
Event.observeを使うのはいいとして、これはbindを使うべきなんだろうか?
無名関数が二重になってるからbind()を使うと余計に分かり難くなる気がする。


もう少し考えよう。。

prototype.jsのbind

hawkさんにコメントをいただいたのでコードの見直し。
どの部分で問題になってたのか忘れてしまったorz


でも数カ所で

obj.func1 = function(element){
  var obj = this;

  Event.observe(element, 'onclick', function(e){ obj.func2(); });
}
obj.func2 = function(){...}

とやってる。
これはbindを使うべき・・なのかな。


bindって使ったことなかったけど

Event.observe(element, ’event’, (function(){...}).bind(element))

function(){...}のthisがelementに固定されるわけか・・。これをelementじゃなくて呼び出しているオブジェクト自身にしようと思えばthisと書くだけでいいのかな?スコープの範囲が変わってるわけではないからthisでいいんだろう(たぶん
bindAsEventListener()も使っていった方がすっきりしそうだけど、もうちょっと理解が追いついてからにしよう・・。

デフォルト動作のキャンセル

onsubmitなどでreturn false;を返すと、デフォルトの動作をキャンセルできる。
で、これもEvent.observe()では無効なのか。
同じイベントに何個でもハンドラを登録できることを考えると普通の動きなのかな。

prototype.jsで綺麗なコードを書く

hawkさんにかなり助けてもらいつつ、コードが綺麗になってきたのでまとめてみる。
Ajaxイン・アクションの復習も兼ねて。


理解できてくると段々楽しくなってくるね。ブラウザの挙動の違いは困るけどprototype.jsが結構吸収してくれてるぽい。


HTMLからJavaScriptのコードを取り除く

知っている人は何を今更と思うかもしれないが、他の言語をやっていてJavaScriptに手を出そうとしたときに気付きにくい。入門書ではほとんどHTMLのタグの中に書いてるんだもの。

Event.observe(window, 'load', function(){
    // 初期化処理
    Initializer.initLink();
    Initializer.initForm();
});

ネットでもbodyタグにonload=hoge() としている例が沢山あるがそれは好ましくない。JavaScriptを外部ファイルにしきれていないのでちゃんと分ける。

オブジェクト指向で書く

上の例だと

var Initializer = {
    initLink: function(){
    },
    initForm: function(){
    }
}

のようなオブジェクトを定義しておいて名前空間を分ける。
または、

function Initializer(){
    this.initLink();
    this.initForm();
}
Initializer.prototype.initLink = function(){
}
Initializer.prototype.initForm = function(){
}

Event.observe(window, 'load', function(){
    var initializer = new Initializer();
});

でも良い。こちらはオブジェクトを複数扱ったり状態を保持したいとき向け。

イベントハンドラはリスナに登録する

汎用的にするには、onclick等に直接書かずにイベントリスナにハンドラ関数を登録する。

var Initializer = {
    initLink: function(){
        Event.observe('ajaxtext', 'click', function(){
            // Ajaxでリンク先を表示したりとか
        });
    },
    initForm: function(){
        Event.observe('form', 'submit', (function(e){
            if (this.checkFormError()){
                // 入力のエラーなど
                $('errorMessage').innerHTML = 'error!';
                Event.stop(e);
            }
        }).bind(this));
    },
    initInfo: function(menuItem){
        Event.observe(menuItem, 'click', this.viewInformation.bind($(menuItem)));
    },
    checkFormError: function(){...},
    viewInformation: function(){...}
}

イベントを登録するにはbindAsEventListener()の方が適しているようだが
http://blog.hawklab.jp/item-62.html
Event.observe()を使うならば必要ないっぽい。実際 bindAsEventListener()内で event||window.event としているだけだった。

$('form').onclick = this.myFunc.bindAsEventListener(this, arg1, arg2);

とobserveを使わない場合には有効だが、こっちを使う必要はなさそう。bind()のソースは$A(arguments)とかやっていてbindAsEventListener()の方はやってないのだが、この違いはまだ理解できていない。

# 追記
# なんか複数の引数(arg1, arg2)を受け取れなかった・・イベントだから引数はeventのみ?


イベントリスナは複数登録できる。onclickに対する動作を一カ所にまとめて書かなくてもいいようになる。
実行される順番は・・・不定だったような。実行される順番はIEのattachEventとaddEventListenerで逆(?)なので、複数登録する場合は互いに独立した関数にする。

おとなり日記で100%を初めて見た


javascriptの作法 - $php→rails (rails に憧れる phper のブログ)
まったく同じようなことで悩んでた様子だ(笑


prototype.jsのbind周辺のソースは難解だわ・・。


関係ないけど検索してたらJavaScriptでカリー化とか懐かしい記事が見つかった。
凄い言語だなこれは。

今日のハマりどころ

prototype.jsのEvent.observe()で登録した関数内のthisの参照する実体がfirefoxIEで違うっぽい。
onclick=function(){...} と直接代入すればthisはどちらもイベントが起きた要素になる。


連想配列PHPのように考えてたら行き詰まった。length使えないのね。
PHP連想配列に慣れてると他の言語の配列は不便に感じる。
JavaScriptのfor〜in構文はよくわからない・・。
簡単な解決方法は無いものか。Hashとか$H()で何か出来そうな・・うーん。