typescript 在 jquery 回调中调用时打字稿“这个”范围问题
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20627138/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
TypeScript "this" scoping issue when called in jquery callback
提问by Jonathan Moffatt
I'm not sure of the best approach for handling scoping of "this" in TypeScript.
我不确定在 TypeScript 中处理“this”范围的最佳方法。
Here's an example of a common pattern in the code I am converting over to TypeScript:
这是我正在转换为 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);
Now, I could change the call to...
现在,我可以将呼叫更改为...
$(document).ready(thisTest.run.bind(thisTest));
...which does work. But it's kinda horrible. It means that code can all compile and work fine in some circumstances, but if we forget to bind the scope it will break.
...确实有效。但这有点可怕。这意味着代码在某些情况下都可以编译并正常工作,但是如果我们忘记绑定作用域,它就会中断。
I would like a way to do it within the class, so that when using the class we don't need to worry about what "this" is scoped to.
我想要一种在类中执行此操作的方法,以便在使用类时我们无需担心“this”的范围。
Any suggestions?
有什么建议?
Update
更新
Another approach that works is using the fat arrow:
另一种有效的方法是使用粗箭头:
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
Is that a valid approach?
这是一种有效的方法吗?
回答by Ryan Cavanaugh
You have a few options here, each with its own trade-offs. Unfortunately there is no obvious best solution and it will really depend on the application.
您在这里有几个选项,每个选项都有自己的权衡。不幸的是,没有明显的最佳解决方案,它实际上取决于应用程序。
Automatic Class Binding
As shown in your question:
自动类绑定
如您的问题所示:
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
- Good/bad: This creates an additional closure per method per instance of your class. If this method is usually only used in regular method calls, this is overkill. However, if it's used a lot in callback positions, it's more efficient for the class instance to capture the
this
context instead of each call site creating a new closure upon invoke. - Good: Impossible for external callers to forget to handle
this
context - Good: Typesafe in TypeScript
- Good: No extra work if the function has parameters
- Bad: Derived classes can't call base class methods written this way using
super.
- Bad: The exact semantics of which methods are "pre-bound" and which aren't create an additional non-typesafe contract between your class and its consumers.
- 好/坏:这会为类的每个实例的每个方法创建一个额外的闭包。如果这个方法通常只用于常规的方法调用,那就有点矫枉过正了。但是,如果它在回调位置中被大量使用,则类实例捕获
this
上下文会更有效,而不是每个调用站点在调用时创建一个新的闭包。 - 好:外部调用者不可能忘记处理
this
上下文 - 好:TypeScript 中的类型安全
- 好:如果函数有参数,则无需额外工作
- 不好:派生类不能调用使用这种方式编写的基类方法
super.
- 不好:哪些方法是“预先绑定”的,哪些方法不会在您的类与其使用者之间创建额外的非类型安全契约的确切语义。
Function.bind
Also as shown:
Function.bind
也如图:
$(document).ready(thisTest.run.bind(thisTest));
- Good/bad: Opposite memory/performance trade-off compared to the first method
- Good: No extra work if the function has parameters
- Bad: In TypeScript, this currently has no type safety
- Bad: Only available in ECMAScript 5, if that matters to you
- Bad: You have to type the instance name twice
- 好/坏:与第一种方法相比,内存/性能的权衡相反
- 好:如果函数有参数,则无需额外工作
- 不好:在 TypeScript 中,目前没有类型安全
- 不好:仅在 ECMAScript 5 中可用,如果这对您很重要
- 错误:您必须键入两次实例名称
Fat arrow
In TypeScript (shown here with some dummy parameters for explanatory reasons):
TypeScript 中的粗箭头(出于解释原因,此处显示了一些虚拟参数):
$(document).ready((n, m) => thisTest.run(n, m));
- Good/bad: Opposite memory/performance trade-off compared to the first method
- Good: In TypeScript, this has 100% type safety
- Good: Works in ECMAScript 3
- Good: You only have to type the instance name once
- Bad: You'll have to type the parameters twice
- Bad: Doesn't work with variadic parameters
- 好/坏:与第一种方法相比,内存/性能的权衡相反
- 好:在 TypeScript 中,它具有 100% 的类型安全性
- 好:适用于 ECMAScript 3
- 好:您只需键入一次实例名称
- 不好:你必须输入两次参数
- 坏:不适用于可变参数
回答by John Weisz
Another solution that requires some initial setup but pays off with its invincibly light, literally one-word syntax is using Method Decoratorsto JIT-bind methods through getters.
另一种需要一些初始设置但以其无敌轻巧、字面意思的单字语法获得回报的解决方案是使用方法装饰器通过 getter 对方法进行 JIT 绑定。
I've created a repo on GitHubto showcase an implementation of this idea (it's a bit lengthy to fit into an answer with its 40 lines of code, including comments), that you would use as simply as:
我在 GitHub 上创建了一个repo来展示这个想法的实现(用 40 行代码来适应答案有点冗长,包括评论),你可以像这样简单地使用:
class DemonstrateScopingProblems {
private status = "blah";
@bound public run() {
alert(this.status);
}
}
I haven't seen this mentioned anywhere yet, but it works flawlessly. Also, there is no notable downside to this approach: the implementation of this decorator -- including some type-checking for runtime type-safety-- is trivial and straightforward, and comes with essentially zero overhead after the initial method call.
我还没有在任何地方看到过这个,但它完美无缺。此外,这种方法没有明显的缺点:这个装饰器的实现——包括一些运行时类型安全的类型检查——是微不足道的和直接的,并且在初始方法调用后基本上为零开销。
The essential part is defining the following getter on the class prototype, which is executed immediately beforethe first call:
基本部分是在类原型上定义以下 getter,它在第一次调用之前立即执行:
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];
}
The idea can be also taken one step further, by doing this in a class decorator instead, iterating over methods and defining the above property descriptor for each of them in one pass.
这个想法还可以更进一步,通过在类装饰器中执行此操作,迭代方法并在一次传递中为每个方法定义上述属性描述符。
回答by Stefan Steiger
Necromancing.
There's an obvious simple solution that doesn't require arrow-functions (arrow-functions are 30% slower), or JIT-methods through getters.
That solution is to bind the this-context in the constructor.
死灵法术。
有一个明显的简单解决方案,不需要箭头函数(箭头函数慢 30%)或通过 getter 的 JIT 方法。
该解决方案是在构造函数中绑定 this-context。
class DemonstrateScopingProblems
{
constructor()
{
this.run = this.run.bind(this);
}
private status = "blah";
public run() {
alert(this.status);
}
}
You can write an autobind method to automatically bind all functions in the constructor of the class:
您可以编写一个 autobind 方法来自动绑定类的构造函数中的所有函数:
class DemonstrateScopingProblems
{
constructor()
{
this.autoBind(this);
}
[...]
}
export function autoBind(self)
{
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
Note that if you don't put the autobind-function into the same class as a member function, it's just autoBind(this);
and not this.autoBind(this);
需要注意的是,如果你不把autobind功能在同一类的成员函数,它只是autoBind(this);
不this.autoBind(this);
And also, the above autoBind function is dumbed down, to show the principle.
If you want this to work reliably, you need to test if the function is a getter/setter of a property as well, because otherwise - boom - if your class contains properties, that is.
而且,上面的 autoBind 函数被简化了,以显示原理。
如果您希望它可靠地工作,则需要测试该函数是否也是属性的 getter/setter,否则 - 繁荣 - 如果您的类包含属性,即。
Like this:
像这样:
export function autoBind(self)
{
for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
{
if (key !== 'constructor')
{
// console.log(key);
let desc = Object.getOwnPropertyDescriptor(self.constructor.prototype, key);
if (desc != null)
{
let g = desc.get != null;
let s = desc.set != null;
if (g || s)
{
if (g)
desc.get = desc.get.bind(self);
if (s)
desc.set = desc.set.bind(self);
Object.defineProperty(self.constructor.prototype, key, desc);
continue; // if it's a property, it can't be a function
} // End if (g || s)
} // End if (desc != null)
if (typeof (self[key]) === 'function')
{
let val = self[key];
self[key] = val.bind(self);
} // End if (typeof (self[key]) === 'function')
} // End if (key !== 'constructor')
} // Next key
return self;
} // End Function autoBind
回答by Albino Cordeiro
In your code, have you tried just changing the last line as follows?
在您的代码中,您是否尝试过按如下方式更改最后一行?
$(document).ready(() => thisTest.run());