在jquery回调中调用时,TypeScript"this"作用域问题

Jon*_*att 105 this typescript

我不确定在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); 
Run Code Online (Sandbox Code Playgroud)

现在,我可以将呼叫改为......

$(document).ready(thisTest.run.bind(thisTest));
Run Code Online (Sandbox Code Playgroud)

......确实有效.但它有点可怕.这意味着代码可以在某些情况下编译和工作正常,但如果我们忘记绑定范围,它将会中断.

我想在类中做一个方法,这样在使用类时我们不需要担心"this"的作用范围.

有什么建议?

更新

另一种方法是使用胖箭头:

class DemonstrateScopingProblems {
    private status = "blah";

    public run = () => {
        alert(this.status);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一种有效的方法吗?

Rya*_*ugh 161

这里有几个选项,每个选项都有自己的权衡.不幸的是,没有明显的最佳解决方案,它将真正取决于应用程序.

自动类绑定
如您的问题所示:

class DemonstrateScopingProblems {
    private status = "blah";

    public run = () => {
        alert(this.status);
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 好/坏:这会为每个类的实例创建一个额外的闭包.如果此方法通常仅用于常规方法调用,则这是过度的.但是,如果它在回调位置中使用了很多,则类实例捕获this上下文而不是每个调用站点在调用时创建新的闭包更有效.
  • 好:外部呼叫者忘记处理this上下文是不可能的
  • 好:TypeScript中的Typesafe
  • 好:如果函数有参数,则无需额外工作
  • 错误:派生类不能调用以这种方式编写的基类方法 super.
  • 错误:哪些方法是"预先绑定"的确切语义,并且不会在您的类与其使用者之间创建额外的非类型安全合同.

Function.bind
如图所示:

$(document).ready(thisTest.run.bind(thisTest));
Run Code Online (Sandbox Code Playgroud)
  • 好/坏:与第一种方法相比,内存/性能折衷相反
  • 好:如果函数有参数,则无需额外工作
  • 错误:在TypeScript中,这当前没有类型安全性
  • 错误:仅在ECMAScript 5中可用,如果这对您很重要
  • 错误:您必须输入两次实例名称

胖箭头
在TypeScript中(为了解释原因,此处显示了一些虚拟参数):

$(document).ready((n, m) => thisTest.run(n, m));
Run Code Online (Sandbox Code Playgroud)
  • 好/坏:与第一种方法相比,内存/性能折衷相反
  • 好:在TypeScript中,这具有100%的类型安全性
  • 好:适用于ECMAScript 3
  • 好的:你只需要输入一次实例名称
  • 不好:你必须输入两次参数
  • 错误:不适用于可变参数


Joh*_*isz 16

另一种解决方案需要一些初始设置,但是凭借其无形的轻巧,字面上的单字语法得到的回报是使用方法装饰器通过getter JIT绑定方法.

我已经在GitHub上创建了一个repo来展示这个想法的实现(它有点冗长以适应其40行代码的答案,包括注释),你可以简单地使用:

class DemonstrateScopingProblems {
    private status = "blah";

    @bound public run() {
        alert(this.status);
    }
}
Run Code Online (Sandbox Code Playgroud)

我还没有在任何地方看到这个,但它完美无瑕.此外,这种方法没有明显的缺点:这个装饰器的实现 - 包括对运行时类型安全的一些类型检查 - 是微不足道和直接的,并且在初始方法调用之后基本上没有开销.

关键部分是在类原型上定义以下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];
}
Run Code Online (Sandbox Code Playgroud)

完整来源


这个想法也可以更进一步,通过在类装饰器中执行此操作,迭代方法并在一次传递中为每个方法定义上述属性描述符.


Ste*_*ger 14

Necromancing.
有一个明显的简单解决方案,不需要箭头函数(箭头函数慢30%),或JIT方法通过getter.
该解决方案是在构造函数中绑定this-context.

class DemonstrateScopingProblems 
{
    constructor()
    {
        this.run = this.run.bind(this);
    }


    private status = "blah";
    public run() {
        alert(this.status);
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以使用此方法自动绑定构造函数中类中的所有函数:

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
Run Code Online (Sandbox Code Playgroud)