TypeScriptで "this"のスコープを処理するための最適なアプローチがわかりません。
TypeScriptに変換するコードの一般的なパターンの例を次に示します。
class DemonstrateScopingProblems {
private status = "blah";
public run() {
alert(this.status);
}
}
var thisTest = new DemonstrateScopingProblems();
// works as expected, displays "blah":
thisTest.run();
// doesn't work; this is scoped to be the document so this.status is undefined:
$(document).ready(thisTest.run);
これで、呼び出しを...
$(document).ready(thisTest.run.bind(thisTest));
...これは動作します。しかし、それはちょっと恐ろしいです。状況によってはコードがすべてコンパイルされて正常に動作できることを意味しますが、スコープのバインドを忘れると破損します。
クラス内でそれを行う方法が欲しいので、クラスを使用するときに「this」のスコープを心配する必要はありません。
助言がありますか?
動作するもう1つのアプローチは、太い矢印を使用することです。
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
それは有効なアプローチですか?
ここにはいくつかのオプションがあり、それぞれにトレードオフがあります。残念ながら、明らかな最善の解決策はなく、アプリケーションに依存します。
自動クラスバインディング
質問のとおり:
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
this
コンテキストをキャプチャする方が効率的です。this
コンテキストの処理を忘れることは不可能super.
を使用してこのように記述された基本クラスメソッドを呼び出すことができませんFunction.bind
また、次のとおりです。
$(document).ready(thisTest.run.bind(thisTest));
太い矢印
TypeScriptで(説明のためにいくつかのダミーパラメーターを使用してここに表示)
$(document).ready((n, m) => thisTest.run(n, m));
初期セットアップを必要とするが、文字通り1ワードの文字通りシンタックスに優れた別のソリューションは、ゲッターを介してJITバインドメソッドに Method Decorators を使用します。
GitHubのレポ を作成して、このアイデアの実装を紹介します(コメントを含む40行のコードで回答に合わせるには少し時間がかかります)、次のように簡単に使用できます。
class DemonstrateScopingProblems {
private status = "blah";
@bound public run() {
alert(this.status);
}
}
これについてはまだ言及していませんが、問題なく動作します。また、このアプローチには顕著なマイナス面はありません:このデコレータの実装-実行時の型安全性のための型チェックを含む-は簡単で簡単であり、初期メソッド呼び出し。
重要な部分は、クラスプロトタイプで次のゲッターを定義することです。これは、最初の呼び出しであるimmediately beforeで実行されます。
get: function () {
// Create bound override on object instance. This will hide the original method on the prototype, and instead yield a bound version from the
// instance itself. The original method will no longer be accessible. Inside a getter, 'this' will refer to the instance.
var instance = this;
Object.defineProperty(instance, propKey.toString(), {
value: function () {
// This is effectively a lightweight bind() that skips many (here unnecessary) checks found in native implementations.
return originalMethod.apply(instance, arguments);
}
});
// The first invocation (per instance) will return the bound method from here. Subsequent calls will never reach this point, due to the way
// JavaScript runtimes look up properties on objects; the bound method, defined on the instance, will effectively hide it.
return instance[propKey];
}
アイデアをさらに一歩進めて、代わりにクラスデコレータでこれを実行し、メソッドを繰り返し処理し、1つのパスで各メソッドの上記のプロパティ記述子を定義します。
ネクロマンシング。
矢印関数(矢印関数は30%遅くなります)やゲッターを介したJITメソッドを必要としない明らかな単純なソリューションがあります。
その解決策は、コンストラクターでthis-contextをバインドすることです。
class DemonstrateScopingProblems
{
constructor()
{
this.run = this.run.bind(this);
}
private status = "blah";
public run() {
alert(this.status);
}
}
このメソッドを使用して、コンストラクター内のクラス内のすべての関数を自動的にバインドできます。
class DemonstrateScopingProblems
{
constructor()
{
this.autoBind(this);
}
[...]
}
export function autoBind(self: any)
{
for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
{
const val = self[key];
if (key !== 'constructor' && typeof val === 'function')
{
// console.log(key);
self[key] = val.bind(self);
} // End if (key !== 'constructor' && typeof val === 'function')
} // Next key
return self;
} // End Function autoBind
あなたのコードでは、最後の行を次のように変更しようとしましたか?
$(document).ready(() => thisTest.run());