简体   繁体   English

如何使用回调函数在TypeScript中保留词法范围

[英]How can I preserve lexical scope in TypeScript with a callback function

I have a TypeScript class, with a function that I intend to use as a callback: 我有一个TypeScript类,有一个我打算用作回调的函数:

removeRow(_this:MyClass): void {
    ...
    // 'this' is now the window object
    // I must use '_this' to get the class itself
    ...
}

I pass it in to another function 我将它传递给另一个函数

this.deleteRow(this.removeRow);

which in turn calls a jQuery Ajax method, which if successful, invokes the callback like this: 反过来调用jQuery Ajax方法,如果成功,则调用这样的回调:

deleteItem(removeRowCallback: (_this:MyClass) => void ): void {
    $.ajax(action, {
        data: { "id": id },
        type: "POST"
    })
    .done(() => {
        removeRowCallback(this);
    })
    .fail(() => {
        alert("There was an error!");
    });
}

The only way I can preserve the 'this' reference to my class is to pass it on to the callback, as demonstrated above. 我可以保留我的类的'this'引用的唯一方法是将其传递给回调,如上所示。 It works, but it's pants code. 它有效,但它是裤子代码。 If I don't wire up the 'this' like this (sorry), then any reference to this in the callback method has reverted to the Window object. 如果我没有像这样连接'this'(抱歉),那么回调方法中对此的任何引用都已恢复为Window对象。 Because I'm using arrow functions all the way, I expected that the 'this' would be the class itself, as it is elsewhere in my class. 因为我一直在使用箭头函数,所以我期望'this'将成为类本身,因为它在我班级的其他地方。

Anyone know how to pass callbacks around in TypeScript, preserving lexical scope? 任何人都知道如何在TypeScript中传递回调,保留词法范围?

Edit 2014-01-28: 编辑2014-01-28:

New readers, make sure you check out Zac's answer below . 新读者,请务必查看下面Zac的答案

He has a much neater solution that will let you define and instantiate a scoped function in the class definition using the fat arrow syntax. 他有一个更简洁的解决方案,允许您使用胖箭头语法在类定义中定义和实例化范围函数。

The only thing I will add is that, in regard to option 5 in Zac's answer, it's possible to specify the method signature and return type without any repetition using this syntax: 我要添加的唯一内容是,关于Zac答案中的选项5 ,可以使用以下语法指定方法签名和返回类型而不重复:

public myMethod = (prop1: number): string => {
    return 'asdf';
}

Edit 2013-05-28: 编辑2013-05-28:

The syntax for defining a function property type has changed (since TypeScript version 0.8). 定义函数属性类型的语法已更改(因为TypeScript版本为0.8)。

Previously you would define a function type like this: 以前你要定义一个这样的函数类型:

class Test {
   removeRow: (): void;
}

This has now changed to: 现在已改为:

class Test {
   removeRow: () => void;
}

I have updated my answer below to include this new change. 我已在下面更新了我的答案,以包含这一新变化。

As a further aside: If you need to define multiple function signatures for the same function name (eg runtime function overloading) then you can use the object map notation (this is used extensively in the jQuery descriptor file): 进一步说:如果需要为同一个函数名定义多个函数签名(例如运行时函数重载),那么可以使用对象映射表示法(这在jQuery描述符文件中广泛使用):

class Test {
    removeRow: {
        (): void;
        (param: string): string;
    };
}

You need to define the signature for removeRow() as a property on your class but assign the implementation in the constructor. 您需要将removeRow()的签名定义为类的属性,但在构造函数中分配实现。

There are a few different ways you can do this. 有几种不同的方法可以做到这一点。

Option 1 选项1

class Test {

    // Define the method signature here.
    removeRow: () => void;

    constructor (){
        // Implement the method using the fat arrow syntax.
        this.removeRow = () => {
            // Perform your logic to remove the row.
            // Reference `this` as needed.
        }
    }

}

If you want to keep your constructor minimal then you can just keep the removeRow method in the class definition and just assign a proxy function in the constructor: 如果你想保持你的构造函数最小,那么你可以将removeRow方法保留在类定义中,只需在构造函数中指定一个代理函数:

Option 2 选项2

class Test {

    // Again, define the method signature here.
    removeRowProxy: () => void;

    constructor (){
        // Assign the method implementation here.
        this.removeRowProxy = () => {
            this.removeRow.apply(this, arguments);
        }
    }

    removeRow(): void {
        // ... removeRow logic here.
    }

}

Option 3 选项3

And finally, if you're using a library like underscore or jQuery then you can just use their utility method to create the proxy: 最后,如果你使用像下划线或jQuery这样的库,那么你可以使用他们的实用工具方法来创建代理:

class Test {

    // Define the method signature here.
    removeRowProxy: () => void;

    constructor (){
        // Use jQuery to bind removeRow to this instance.
        this.removeRowProxy = $.proxy(this.removeRow, this);
    }

    removeRow(): void {
        // ... removeRow logic here.
    }

}

Then you can tidy up your deleteItem method a bit: 然后你可以deleteItem整理一下deleteItem方法:

// Specify `Function` as the callback type.
// NOTE: You can define a specific signature if needed.
deleteItem(removeRowCallback: Function ): void {
    $.ajax(action, {
        data: { "id": id },
        type: "POST"
    })

    // Pass the callback here.
    // 
    // You don't need the fat arrow syntax here
    // because the callback has already been bound
    // to the correct scope.
    .done(removeRowCallback)

    .fail(() => {
        alert("There was an error!");
    });
}

UPDATE: See Sly's updated answer. 更新:请参阅Sly的更新答案。 It incorporates an improved version of the options below. 它包含以下选项的改进版本。

ANOTHER UPDATE: Generics 另一个更新:泛型

Sometimes you want to specify a generic type in a function signature without having to specify it on the the whole class. 有时您希望在函数签名中指定泛型类型,而不必在整个类中指定它。 It took me a few tries to figure out the syntax, so I thought it might be worth sharing: 我花了几次尝试来弄清楚语法,所以我认为值得分享:

class MyClass {  //no type parameter necessary here

     public myGenericMethod = <T>(someArg:string): QPromise<T> => {

         //implementation here...

     }
}

Option 4 选项4

Here are a couple more syntaxes to add to Sly_cardinal's answer. 这里有几个语法可以添加到Sly_cardinal的答案中。 These examples keep the function declaration and implementation in the same place: 这些示例将函数声明和实现保持在同一位置:

class Test {

      // Define the method signature AND IMPLEMENTATION here.
      public removeRow: () => void = () => {

        // Perform your logic to remove the row.
        // Reference `this` as needed.

      }

      constructor (){

      }

} }

or 要么

Option 5 选项5

A little more compact, but gives up explicit return type (the compiler should infer the return type anyway if not explicit): 稍微紧凑,但放弃显式返回类型(如果不明确,编译器应该推断返回类型):

class Test {

      // Define implementation with implicit signature and correct lexical scope.
      public removeRow = () => {

        // Perform your logic to remove the row.
        // Reference `this` as needed.

      }

      constructor (){

      }

} }

Use .bind() to preserve context within the callback. 使用.bind()来保留回调中的上下文。

Working code example: 工作代码示例:

window.addEventListener(
  "resize",
  (()=>{this.retrieveDimensionsFromElement();}).bind(this)
)

The code in original question would become something like this: 原始问题中的代码将变为如下所示:

$.ajax(action, {
    data: { "id": id },
    type: "POST"
})
.done(
  (() => {
    removeRowCallback();
  }).bind(this)
)

It will set the context (this) inside the callback function to whatever was passed as an argument to bind function, in this case the original this object. 它会将回调函数中的上下文(this)设置为作为bind函数的参数传递的内容,在本例中是原始的this对象。

This is sort of a cross post from another answer ( Is there an alias for 'this' in TypeScript? ). 这是另一个答案的交叉帖子( TypeScript中是否存在'this'的别名? )。 I re-applied the concept using the examples from above. 我使用上面的例子重新应用了这个概念。 I like it better than the options above because it explictly supports "this" scoping to both the class instance as well as the dynamic context entity that calls the method. 我比上面的选项更喜欢它,因为它明确地支持类实例以及调用该方法的动态上下文实体的“this”范围。

There are two versions below. 下面有两个版本。 I like the first one because the compiler assists in using it correctly (you won't as easily try to misuse the callback lambda itself as the callback, because of the explicitly typed parameter). 我喜欢第一个,因为编译器帮助正确使用它(你不会轻易地尝试滥用回调lambda本身作为回调,因为显式类型参数)。

Test it out: http://www.typescriptlang.org/Playground/ 测试出来: http//www.typescriptlang.org/Playground/

class Test {

    private testString: string = "Fancy this!";

    // Define the method signature here.
    removeRowLambdaCallback(outerThis: Test): {(): void} {
        alert("Defining callback for consumption");
        return function(){
            alert(outerThis.testString); // lexically scoped class instance
            alert(this); // dynamically scoped context caller
            // Put logic here for removing rows. Can refer to class
            // instance as well as "this" passed by a library such as JQuery or D3.
        }
    }
    // This approach looks nicer, but is more dangerous
    // because someone might use this method itself, rather
    // than the return value, as a callback.
     anotherRemoveRowLambdaCallback(): {(): void} {
        var outerThis = this;
        alert("Defining another callback for consumption");
        return function(){
            alert(outerThis.testString); // lexically scoped class instance
            alert(this); // dynamically scoped context caller
            // Put logic here for removing rows. Can refer to class
            // instance as well as "this" passed by a library such as JQuery or D3.
        }
    }
}

var t = new Test();
var callback1 = t.removeRowLambdaCallback(t);
var callback2 = t.anotherRemoveRowLambdaCallback();

callback1();
callback2();

Building upon sly and Zac's answers with types: A complete hello world example. 建立在狡猾和Zac的类型答案之上:一个完整​​的问候世界的例子。 I hope this is welcome, seeing as this is the top result in Google, when searching for "typescript javascript callbacks" 我希望这是受欢迎的,因为这是Google搜索“typescript javascript回调”时的最佳结果

type MyCallback = () => string;

class HelloWorld {

    // The callback
    public callback: MyCallback = () => {
        return 'world';
    }

    // The caller
    public caller(callback: MyCallback) {
        alert('Hello ' + callback());
    }
}

let hello = new HelloWorld();
hello.caller(hello.callback);

This gets transpiled into: 这被转化为:

var HelloWorld = (function () {
    function HelloWorld() {
        // The callback
        this.callback = function () {
            return 'world';
        };
    }
    // The caller
    HelloWorld.prototype.caller = function (callback) {
        alert('Hello ' + callback());
    };
    return HelloWorld;
}());
var hello = new HelloWorld();
hello.caller(hello.callback);

Hope someone finds it just a little useful. 希望有人觉得它有点用处。 :) :)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM