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

TypeScript “this” scoping issue when called in jquery callback

我不确定在typescript中处理"this"范围的最佳方法。

下面是我要转换为typescript的代码中的一个常见模式示例:

1
2
3
4
5
6
7
8
9
10
11
12
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);

现在,我可以把电话改成…

1
$(document).ready(thisTest.run.bind(thisTest));

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

我希望有一种方法可以在类内完成,这样当使用类时,我们就不必担心"this"的作用域是什么。

有什么建议吗?

更新

另一个有效的方法是使用胖箭头:

1
2
3
4
5
6
7
class DemonstrateScopingProblems {
    private status ="blah";

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

这是一个有效的方法吗?


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

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

1
2
3
4
5
6
7
class DemonstrateScopingProblems {
    private status ="blah";

    public run = () => {
        alert(this.status);
    }
}
  • 好/坏:这会为每个类实例的每个方法创建一个额外的闭包。如果此方法通常只在常规方法调用中使用,则这是多余的。但是,如果在回调位置使用了很多,那么类实例捕获this上下文的效率会更高,而不是每个调用站点在调用时创建一个新的闭包。
  • 好:外部呼叫者不可能忘记处理this上下文
  • 好的:在typescript中使用typesafe
  • 好:如果函数有参数,就没有额外的工作
  • 坏:派生类不能调用使用super.以这种方式编写的基类方法
  • 错误:哪些方法是"预绑定"的,哪些方法不会在类与其使用者之间创建额外的非类型安全协定。

函数绑定同样如图所示:

1
$(document).ready(thisTest.run.bind(thisTest));
  • 好/坏:与第一种方法相比,内存/性能的权衡正好相反
  • 好:如果函数有参数,就没有额外的工作
  • 错误:在typescript中,当前没有类型安全性
  • 坏:只有在EcmaScript 5中可用,如果这对您很重要的话
  • 错误:必须键入两次实例名

宽箭头在typescript中(由于解释原因,这里显示了一些虚拟参数):

1
$(document).ready((n, m) => thisTest.run(n, m));
  • 好/坏:与第一种方法相比,内存/性能的权衡正好相反
  • 很好:在typescript中,这有100%的类型安全性
  • 好:在ecmascript 3中工作
  • 好:您只需键入一次实例名
  • 错误:您必须键入两次参数
  • 坏:不适用于可变参数


亡灵术。< BR>有一个明显的简单解决方案不需要箭头函数(箭头函数慢30%),也不需要通过getter的jit方法。
该解决方案是在构造函数中绑定此上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
class DemonstrateScopingProblems
{
    constructor()
    {
        this.run = this.run.bind(this);
    }


    private status ="blah";
    public run() {
        alert(this.status);
    }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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


另一种解决方案需要一些初始设置,但却以其无敌的轻量级获得回报,字面上来说,一个词的语法是使用方法修饰器通过getter来jit绑定方法。

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

1
2
3
4
5
6
7
class DemonstrateScopingProblems {
    private status ="blah";

    @bound public run() {
        alert(this.status);
    }
}

我还没有看到这上面提到过,但它工作得很完美。另外,这种方法也没有明显的缺点:这个修饰符的实现——包括对运行时类型安全性的一些类型检查——是简单易行的,并且在初始方法调用之后基本上没有开销。

基本部分是在类原型上定义以下getter,它在第一次调用之前立即执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
$(document).ready(() => thisTest.run());