イナヅマTVログ

[JavaScript] this参照が変わらないようにsetTimeoutで関数を実行したい

| 0件のコメント

*投稿タイトル変えました

setTimeoutを多用したコードを書いています。

少し遅延実行したり、大量の関数を何となく同時に実行させたり、とかとか。

そのとき困るのが setTimeout で遅延実行された関数内の this 参照が呼び出された関数のもので無くなること。
this参照が別物になると困るので無い知恵しぼって考えました。

(function (){
    "use strict";
 
    function Example () {
        this.x = 10;
        this.y = 20;
    }
 
    Example.prototype.doDelay = function (){
        var me = this;
 
        function delay () {
            me.doSomething();
        }
 
        setTimeout(delay, 1000);
    };
 
    Example.prototype.doSomething = function (){
        // 10, 20
        console.log(this.x, this.y);
    };
 
    var example = new Example();
    example.doDelay();
}());

thisはexampleインスタンスを参照しているので期待した値をログ出力してくれます。

でもあっちこっちで毎回同じようなことを書いてるのがめんどくさくなってきた。

そうだ、汎用関数を作ろう

というわけで汎用的に使える関数を作りました。

/**
 *
 * @param {Function} callback require
 * @param {instance=this} scope option default this
 * @param {Number=1} second option default 1
 * @param {Array=[]} args option default []
 */
function delayCall (callback, scope, second, args) {
 
    if (typeof scope === "undefined" || scope === null) {
        scope = this;
    }
 
    if (!(!isNaN(parseFloat(second)) && isFinite(second))) {
        second = 1;
    }
 
    if (!Array.isArray(args)) {
        if (typeof args !== "undefined") {
            args = [args];
        } else {
            args = [];
        }
    }
 
    setTimeout(
            function (){
                callback.apply(scope, args);
            },
            1000 * second
    );
}

汎用性を持たせようとすると引数チェックとか入るから、長くなっちゃうなー

【使い方】

(function (){
 
    function Example () {
        this.x = 30;
        this.y = 50;
    }
 
    Example.prototype.doDelayShort = function (){
        delayCall(this.doSomethingShort, this, "a");
    };
 
    Example.prototype.doDelayMiddle = function (fruits, price){
        delayCall(this.doSomethingMiddle, this, 2, [fruits, price]);
    };
 
    Example.prototype.doDelayLong = function (fruits){
        delayCall(this.doSomethingLong, this, 3, fruits);
    };
 
    // callbacks
    Example.prototype.doSomethingShort = function (){
        console.log(this.x, this.y);
    };
 
    Example.prototype.doSomethingMiddle = function (fruits, price){
        console.log(fruits, price);
    };
 
    Example.prototype.doSomethingLong = function (fruits){
        console.log(fruits);
    };
 
    var example = new Example();
    // 30,50
    example.doDelayShort();
 
    // apple 100円 
    example.doDelayMiddle("apple", "100円");
 
    //["apple", "100円"] "orange" 
    example.doDelayMiddle(["apple", "100円"], "orange");
 
    // Object {orange: "orange", apple: "apple"}  
    example.doDelayLong({orange: "orange", apple: "apple"});
}());

できた!

【参考資料】
MDN: window.setTimeout
「this 問題」

MDN: this 「メソッドの束縛」

[inazumatv.com] 過去の投稿
[JavaScript]Function.prototype.callとFunction.prototype.applyとか
[JavaScript]デフォルト引数の設定方法って難しい
ECMAScript 5, Array.isArrayを非対応ブラウザでも使いたい
[JavaScript] 条件判定には===, !==を使いましょ

【おまけ】
Underscore.jsbindを使えば済む話だけども

コメントを残す

必須欄は * がついています