web-dev-qa-db-ja.com

TypeScriptに「this」のエイリアスはありますか?

JQueryイベントへのイベントハンドラコールバックとして機能するメソッドが定義されているTypeScriptでクラスを記述しようとしました。

class Editor {
    textarea: JQuery;

    constructor(public id: string) {
        this.textarea = $(id);
        this.textarea.focusin(onFocusIn);
    }

    onFocusIn(e: JQueryEventObject) {
        var height = this.textarea.css('height'); // <-- This is not good.
    }
}

OnFocusInイベントハンドラー内で、TypeScriptは「this」をクラスの「this」と見なします。ただし、jQueryはthis参照をオーバーライドし、イベントに関連付けられたDOMオブジェクトに設定します。

1つの代替方法は、コンストラクター内でラムダをイベントハンドラーとして定義することです。この場合、TypeScriptは非表示の_thisエイリアスを使用して一種のクロージャーを作成します。

class Editor {
    textarea: JQuery;

    constructor(public id: string) {
        this.textarea = $(id);
        this.textarea.focusin((e) => {
            var height = this.textarea.css('height'); // <-- This is good.
        });
    }
}

私の質問は、このjQueryの動作を克服するために、TypeScriptを使用してメソッドベースのイベントハンドラー内でthis参照にアクセスする別の方法はありますか?

73
Todd

そのため、前述のように、メソッドが常にthisポインターにバインドされるようにするTypeScriptメカニズムはありません(これは単なるjQueryの問題ではありません)。この問題に対処します。必要なのは、コールバックを呼び出す前にthisポインターを復元するメソッドのプロキシを生成することです。その後、イベントに渡す前に、そのプロキシでコールバックをラップする必要があります。 jQueryには、jQuery.proxy()と呼ばれる組み込みのメカニズムがあります。そのメソッドを使用した上記のコードの例を次に示します(追加された$.proxy()呼び出しに注意してください)。

class Editor { 
    textarea: JQuery; 

    constructor(public id: string) { 
        this.textarea = $(id); 
        this.textarea.focusin($.proxy(onFocusIn, this)); 
    } 

    onFocusIn(e: JQueryEventObject) { 
        var height = this.textarea.css('height'); // <-- This is not good. 
    } 
} 

それは合理的な解決策ですが、個人的には、開発者がプロ​​キシ呼び出しを含めることを忘れることが多いため、この問題に対するTypeScriptベースの代替解決策を考え出しました。使用すると、必要なすべての下のHasCallbacksクラスはHasCallbacksからクラスを派生させることであり、'cb_'で始まるメソッドはthisポインターを永続的にバインドします。別のthisポインターを使用してそのメソッドを呼び出すことはできませんが、ほとんどの場合この方法をお勧めします。どちらのメカニズムも機能するため、使いやすいと思われる方が機能します。

class HasCallbacks {
    constructor() {
        var _this = this, _constructor = (<any>this).constructor;
        if (!_constructor.__cb__) {
            _constructor.__cb__ = {};
            for (var m in this) {
                var fn = this[m];
                if (typeof fn === 'function' && m.indexOf('cb_') == 0) {
                    _constructor.__cb__[m] = fn;                    
                }
            }
        }
        for (var m in _constructor.__cb__) {
            (function (m, fn) {
                _this[m] = function () {
                    return fn.apply(_this, Array.prototype.slice.call(arguments));                      
                };
            })(m, _constructor.__cb__[m]);
        }
    }
}

class Foo extends HasCallbacks  {
    private label = 'test';

    constructor() {
        super();

    }

    public cb_Bar() {
        alert(this.label);
    }
}

var x = new Foo();
x.cb_Bar.call({});
21
Steven Ickman

矢印関数の構文() => { ... }を使用する場合、thisのスコープは保持されます-これは、 JavaScriptプログラマー向けTypeScript からの例です。

var ScopeExample = { 
  text: "Text from outer function", 
  run: function() { 
    setTimeout( () => { 
      alert(this.text); 
    }, 1000); 
  } 
};

this.textText from outer functionを与えることに注意してください。これは、矢印関数の構文が「字句スコープ」を保持しているためです。

97
Fenton

他のいくつかの回答で説明したように、矢印構文を使用して関数を定義すると、thisへの参照が常に外側のクラスを参照します。

したがって、あなたの質問に答えるために、2つの簡単な回避策があります。

矢印構文を使用してメソッドを参照します

constructor(public id: string) {
    this.textarea = $(id);
    this.textarea.focusin(e => this.onFocusIn(e));
}

矢印構文を使用してメソッドを定義する

onFocusIn = (e: JQueryEventObject) => {
    var height = this.textarea.css('height');
}
20
Sam

コンストラクターでメンバー関数をそのインスタンスにバインドできます。

class Editor {
    textarea: JQuery;

    constructor(public id: string) {
        this.textarea = $(id);
        this.textarea.focusin(onFocusIn);
        this.onFocusIn = this.onFocusIn.bind(this); // <-- Bind to 'this' forever
    }

    onFocusIn(e: JQueryEventObject) {
        var height = this.textarea.css('height');   // <-- This is now fine
    }
}

または、ハンドラーを追加するときにバインドします。

        this.textarea.focusin(onFocusIn.bind(this));
6
Drew Noakes

これを試して

class Editor 
{

    textarea: JQuery;
    constructor(public id: string) {
        this.textarea = $(id);
        this.textarea.focusin((e)=> { this.onFocusIn(e); });
    }

    onFocusIn(e: JQueryEventObject) {
        var height = this.textarea.css('height'); // <-- This will work
    }

}
3
Gabriel C.

Steven Ickmanのソリューションは便利ですが、不完全です。ダニー・ベケットとサムの答えはより短く、より手作業であり、動的で字句的にスコープされた「this」の両方を同時に必要とするコールバックを持つという一般的なケースでは失敗します。以下の説明がTL; DRの場合、コードにスキップします...

ライブラリコールバックで使用するために動的スコープの「this」を保持する必要があります。andクラスインスタンスへの字句スコープの「this」が必要です。インスタンスをコールバックジェネレーターに渡して、クラスインスタンスでパラメーターを効果的に閉じることが最もエレガントだと思います。コンパイラーは、あなたがそうし損なった場合に通知します。レキシカルスコープのパラメーター「outerThis」を呼び出す規則を使用しますが、「self」または別の名前の方が適している場合があります。

「this」キーワードの使用はOO worldから盗まれ、TypeScriptがそれを採用したとき(ECMAScript 6仕様から推測))、レキシカルスコープコンセプトとダイナミックスコープコンセプトを混同し、メソッドが別のエンティティによって呼び出されたときはいつでも、私はこれに少し困惑します;レキシカルスコープのオブジェクトインスタンスを渡すことができるように、TypeScriptで「self」キーワードを好むでしょう。必要なときに明示的な最初の位置の「呼び出し元」パラメーターが必要です(したがって、すべてのWebページが一気に壊れます)。

これが私のソリューションです(大規模なクラスから抜粋)。特にメソッドの呼び出し方法、特に「dragmoveLambda」の本体に目を向けてください。

export class OntologyMappingOverview {

initGraph(){
...
// Using D3, have to provide a container of mouse-drag behavior functions
// to a force layout graph
this.nodeDragBehavior = d3.behavior.drag()
        .on("dragstart", this.dragstartLambda(this))
        .on("drag", this.dragmoveLambda(this))
        .on("dragend", this.dragendLambda(this));

...
}

dragmoveLambda(outerThis: OntologyMappingOverview): {(d: any, i: number): void} {
    console.log("redefine this for dragmove");

    return function(d, i){
        console.log("dragmove");
        d.px += d3.event.dx;
        d.py += d3.event.dy;
        d.x += d3.event.dx;
        d.y += d3.event.dy; 

        // Referring to "this" in dynamic scoping context
        d3.select(this).attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

        outerThis.vis.selectAll("line")
            .filter(function(e, i){ return e.source == d || e.target == d; })
            .attr("x1", function(e) { return e.source.x; })
            .attr("y1", function(e) { return e.source.y; })
            .attr("x2", function(e) { return e.target.x; })
            .attr("y2", function(e) { return e.target.y; });

    }
}

dragging: boolean  =false;
// *Call* these callback Lambda methods rather than passing directly to the callback caller.
 dragstartLambda(outerThis: OntologyMappingOverview): {(d: any, i: number): void} {
        console.log("redefine this for dragstart");

        return function(d, i) {
            console.log("dragstart");
            outerThis.dragging = true;

            outerThis.forceLayout.stop();
        }
    }

dragendLambda(outerThis: OntologyMappingOverview): {(d: any, i: number): void}  {
        console.log("redefine this for dragend");

        return function(d, i) {
            console.log("dragend");
            outerThis.dragging = false;
            d.fixed = true;
        }
    }

}
3
Eric

TypeScriptは、(通常のJavaScriptの手段を超えて)太い矢印のラムダ構文で提供されるthis再マッピングの便利さ以外の「実際の」this参照に戻るための追加の方法を提供しません。既存のJSコードが=>式を使用できなかったため、後方互換性の観点。

CodePlexサイトに提案を投稿することもできますが、言語設計の観点からは、おそらくここでcanが発生することはほとんどありません。

2
Ryan Cavanaugh

Js eval関数を使用できます:var realThis = eval('this');

2
reptile

同様の問題に直面しました。 thisを後のイベントの異なる値として保持するために、多くの場合.each()を使用できると思います。

JavaScriptの方法:

$(':input').on('focus', function() {
  $(this).css('background-color', 'green');
}).on('blur', function() {
  $(this).css('background-color', 'transparent');
});

TypeScriptの方法:

$(':input').each((i, input) => {
  var $input = $(input);
  $input.on('focus', () => {
    $input.css('background-color', 'green');
  }).on('blur', () => {
    $input.css('background-color', 'transparent');
  });
});

これが誰かの助けになることを願っています。

1
obrys

上記のすべての答えよりもはるかに簡単な解決策があります。基本的に、「this」をクラス「this」に変換する「=>」コンストラクトを使用する代わりに、キーのWord関数を使用してJavaScriptにフォールバックします。

class Editor {
    textarea: JQuery;

    constructor(public id: string) {
        var self = this;                      // <-- This is save the reference
        this.textarea = $(id);
        this.textarea.focusin(function() {   // <-- using  javascript function semantics here
             self.onFocusIn(this);          //  <-- 'this' is as same as in javascript
        });
    }

    onFocusIn(jqueryObject : JQuery) {
        var height = jqueryObject.css('height'); 
    }
}
0
Derek Liang

このブログ投稿を確認してください http://lumpofcode.blogspot.com/2012/10/TypeScript-Dart-google-web-toolkit-and.html 、TypeScriptクラス内およびTypeScriptクラス間で呼び出しを整理するための手法について詳しく説明しています。

0
Ezward

thisへの参照を別の変数に保存することもできます。おそらくselfで、その方法で参照にアクセスできます。 TypeScriptは使用していませんが、これは過去にVanilla javascriptで成功していた方法です。

0
Sheridan Bulger