Jul 31, 2005

Function.prototypeを拡張して遅延実行を実現する

JavaScriptにおいて関数というのはFunctionオブジェクトで、他のビルトインオブジェクトと同様に、組み込みのメソッドがある。これがapplyとcallしかないのだけれど、こんな感じに使う。

func.apply(thisObj,arguments)
func.call(thisObj,arg1,arg2,arg3)

thisObjには、その関数内で「this」として使うオブジェクトを指定する。applyの第二引数はargumentsオブジェクトを指定する。配列か、現在実行中の関数のargumentsオブジェクトを丸ごと別の関数に引き渡せる。つまり引数の長さが良くわかってなくても使える。

callは代わりに
func.apply(thisObj,[arg1,arg2,arg3])
と書けるので、実はいらないんじゃないかと思う。

これらは多分、ふつうにJavaScriptを書く上では全然必要でないし、知らなければ知らないでまったく問題にはならない。リファレンスなんかを見ても、リファレンス的な解説のみで具体的な使い方は書いていないと思う。

有名なのは引数の束縛とかそういうやつなのだけれど
http://www.interq.or.jp/student/exeal/dss/ejs/1/4.html

正直、これを読んでもどういうメリットがあるのかいまいち良くわからなかった。
でも、知っていれば知っているなりの書き方がある。

で、具体的に作ったのがこれ。
Object.extend = function(destination, source) {
    for (property in source) {
        if(source.hasOwnProperty(property)){
            destination[property] = source[property];
        }
    }
    return destination;
}
Object.prototype.extend = function(object) {
    return Object.extend.apply(this, [this, object]);
}
Function.prototype.extend({
    later : function(ms){
        var self = this;
        var func = function(){
            var arg = func.arguments;
            var apply_to = this;
            var later_func = function(){
                self.apply(apply_to,arg)
            };
            setTimeout(later_func,ms);
        };
        return func;
    }
});

var func = function(v){alert(v)};

func.later(1000)("1秒後に警告");
func.later(2000)("2秒後に警告");
func.later(1000).later(2000)("3秒後に警告");

(function(v){alert(v);arguments.callee.later(1000)(v)}).later(1000)("1秒ごとに実行")


Function.prototypeを拡張して、laterメソッドを加えてやる。加えるというのは正確ではなく、func.laterが見つからなかった場合に、Function.prototype.laterが実行されるようになる。これだけで、いちいち無名関数作ってsetTimeoutなんて書かなくても、あらゆる関数を簡単に遅延実行させることが出来るようになる。

Object.extendは便利だから使いまくる方向で。でもprototypeはコピーしないように改造した。
何をするコードかというと、元の関数を「1秒後に実行する関数」を生成して、返ってきた関数に引数を渡して実行する。この例だと引数は一つだけれど、複数の引数を必要とする関数でも同様に可能だ。
「1秒後に実行」ではないので、生成された関数をさらに加工することが出来る。三番目は1秒後に実行される関数をさらに2秒遅らせて実行する。

4番目はこれでもいい。
var interval = 1000;
(function(v){alert(v);arguments.callee.later(interval)(v)}).later(interval)("1秒ごとに実行");


実行中にinterval = 3000とかやったら実行中に外から周期を変更できる。arguments.calleeは現在実行されている関数の参照を表していて、これを使うと再起処理なんかをするのに関数名を決めうちにしなくて良い。

とまあ、こんな具合。

----
追記
later(1000).apply(this,arg)とかが動くように、ちょいと直した。
1秒後に別のオブジェクトに変形するオブジェクトとか作れる。
Posted at 05:59 | WriteBacks (5) | Edit
Edit this entry...

wikieditish message: Ready to edit this entry.
















A quick preview will be rendered here when you click "Preview" button.