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参照にアクセスする別の方法はありますか?
そのため、前述のように、メソッドが常に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({});
矢印関数の構文() => { ... }
を使用する場合、this
のスコープは保持されます-これは、 JavaScriptプログラマー向けTypeScript からの例です。
var ScopeExample = {
text: "Text from outer function",
run: function() {
setTimeout( () => {
alert(this.text);
}, 1000);
}
};
this.text
はText from outer function
を与えることに注意してください。これは、矢印関数の構文が「字句スコープ」を保持しているためです。
他のいくつかの回答で説明したように、矢印構文を使用して関数を定義すると、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');
}
コンストラクターでメンバー関数をそのインスタンスにバインドできます。
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));
これを試して
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
}
}
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;
}
}
}
TypeScriptは、(通常のJavaScriptの手段を超えて)太い矢印のラムダ構文で提供されるthis
再マッピングの便利さ以外の「実際の」this
参照に戻るための追加の方法を提供しません。既存のJSコードが=>
式を使用できなかったため、後方互換性の観点。
CodePlexサイトに提案を投稿することもできますが、言語設計の観点からは、おそらくここでcanが発生することはほとんどありません。
Js eval関数を使用できます:var realThis = eval('this');
同様の問題に直面しました。 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');
});
});
これが誰かの助けになることを願っています。
上記のすべての答えよりもはるかに簡単な解決策があります。基本的に、「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');
}
}
このブログ投稿を確認してください http://lumpofcode.blogspot.com/2012/10/TypeScript-Dart-google-web-toolkit-and.html 、TypeScriptクラス内およびTypeScriptクラス間で呼び出しを整理するための手法について詳しく説明しています。
this
への参照を別の変数に保存することもできます。おそらくself
で、その方法で参照にアクセスできます。 TypeScriptは使用していませんが、これは過去にVanilla javascriptで成功していた方法です。