TypeScript";这";在jquery回调中调用时的作用域问题

TypeScript";这";在jquery回调中调用时的作用域问题,typescript,this,Typescript,This,我不确定在TypeScript中处理“this”范围的最佳方法 下面是我转换为TypeScript的代码中的一个常见模式示例: class DemonstrateScopingProblems { private status = "blah"; public run() { alert(this.status); } } var thisTest = new DemonstrateScopingProblems(); // work

我不确定在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”的范围是什么

有什么建议吗

更新 另一种有效的方法是使用fat箭头:

class DemonstrateScopingProblems {
    private status = "blah";

    public run = () => {
        alert(this.status);
    }
}

这是一种有效的方法吗?

这里有几个选项,每个选项都有自己的权衡。不幸的是,没有明显的最佳解决方案,这实际上取决于应用程序

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

class DemonstrateScopingProblems {
    private status = "blah";

    public run = () => {
        alert(this.status);
    }
}
  • 好/坏:这会为类的每个实例的每个方法创建一个额外的闭包。如果这个方法通常只在常规方法调用中使用,那就太过分了。但是,如果在回调位置大量使用它,则类实例捕获
    上下文比每个调用站点在调用时创建新的闭包更有效
  • 好:外部调用方不可能忘记处理此上下文
  • 好:TypeScript中的Typesafe
  • 好:如果函数有参数,则无需额外工作
  • 错误:派生类无法调用使用
    super以这种方式编写的基类方法。
  • 坏:哪些方法是“预绑定”的,哪些方法不会在类及其使用者之间创建额外的非类型安全契约的确切语义
函数.绑定
也如图所示:

$(document).ready(thisTest.run.bind(thisTest));
  • 好/坏:与第一种方法相比,内存/性能的权衡正好相反
  • 好:如果函数有参数,则无需额外工作
  • 错误:在TypeScript中,当前没有类型安全性
  • 坏:只有在ECMAScript 5中才可用,如果这对您很重要的话
  • 错误:必须键入实例名称两次
胖箭头
在TypeScript中(出于解释原因,此处显示了一些伪参数):

  • 好/坏:与第一种方法相比,内存/性能的权衡正好相反
  • 好:在TypeScript中,这具有100%的类型安全性
  • 好:适用于ECMAScript 3
  • 好:只需键入一次实例名称
  • 错误:您必须键入两次参数
  • 坏:不适用于可变参数

另一个解决方案需要一些初始设置,但却以其无与伦比的轻量级(字面上只有一个词的语法)获得了回报,它用于通过getter来JIT绑定方法

我创建了一个示例来展示这一想法的实现(在包含40行代码(包括注释)的答案中有点冗长),您可以简单地使用它:

class DemonstrateScopingProblems {
    private status = "blah";

    @bound public run() {
        alert(this.status);
    }
}
我还没有看到任何地方提到过这一点,但它工作得完美无缺。此外,这种方法没有明显的缺点:这个装饰器的实现——包括一些运行时类型安全性的类型检查——是简单而直接的,并且在初始方法调用之后基本上没有开销

基本部分是在类原型上定义以下getter,该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];
}



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

在代码中,您是否尝试按如下方式更改最后一行

$(document).ready(() => thisTest.run());
巫术
有一个显而易见的简单解决方案,它不需要箭头函数(箭头函数慢30%),也不需要通过getter使用JIT方法。
该解决方案是在构造函数中绑定此上下文

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)
{
    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
请注意,如果您没有将autobind函数与成员函数放在同一个类中,那么它只是
autobind(这个)而非
this.autoBind(this)

同时,将上述自动绑定功能简化,以显示其原理
如果您想让它可靠地工作,您需要测试该函数是否也是属性的getter/setter,否则-boom-如果您的类包含属性,也就是说

像这样:

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

+1个很好的答案Ryan,喜欢利弊分析,谢谢在Function.bind中,每次需要附加事件时,都会创建一个新的闭包。胖箭头刚好完成了此操作!!:D:D=()=>非常感谢D@ryan-卡瓦诺关于对象何时被释放的好坏如何?在SPA活动时间>30分钟的示例中,上面哪一项最适合JS垃圾收集器处理?当类实例可以释放时,所有这些都可以释放。如果事件处理程序的生存期较短,则后两个将更早释放。一般来说,我会说不会有可测量的差异。这会很有帮助:注意:Ryan将他的答案复制到了。寻找TypeScript 2+解决方案。这正是我需要的!我不得不使用“自动绑定(this)”而不是“this.autoBind(this)”@johnopeinto:yep,this.autoBind(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