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

Ral*_*lle 37 javascript callback typescript

我有一个TypeScript类,有一个我打算用作回调的函数:

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

我将它传递给另一个函数

this.deleteRow(this.removeRow);
Run Code Online (Sandbox Code Playgroud)

反过来调用jQuery Ajax方法,如果成功,则调用这样的回调:

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

我可以保留我的类的'this'引用的唯一方法是将其传递给回调,如上所示.它有效,但它是裤子代码.如果我没有像这样连接'this'(抱歉),那么回调方法中对此的任何引用都已恢复为Window对象.因为我一直在使用箭头函数,所以我期望'this'将是类本身,因为它在我班级的其他地方.

任何人都知道如何在TypeScript中传递回调,保留词法范围?

Sly*_*nal 56

编辑2014-01-28:

新读者,请务必查看下面Zac的答案.

他有一个更简洁的解决方案,允许您使用胖箭头语法在类定义中定义和实例化范围函数.

我要添加的唯一内容是,关于Zac答案中的选项5,可以使用以下语法指定方法签名和返回类型而不重复:

public myMethod = (prop1: number): string => {
    return 'asdf';
}
Run Code Online (Sandbox Code Playgroud)

编辑2013-05-28:

定义函数属性类型的语法已更改(自TypeScript版本0.8起).

以前你要定义一个这样的函数类型:

class Test {
   removeRow: (): void;
}
Run Code Online (Sandbox Code Playgroud)

现在已改为:

class Test {
   removeRow: () => void;
}
Run Code Online (Sandbox Code Playgroud)

我已在下面更新了我的答案,以包含这一新变化.

进一步说:如果需要为同一个函数名定义多个函数签名(例如运行时函数重载),那么可以使用对象映射表示法(这在jQuery描述符文件中广泛使用):

class Test {
    removeRow: {
        (): void;
        (param: string): string;
    };
}
Run Code Online (Sandbox Code Playgroud)

您需要在类上定义签名removeRow()作为属性,但在构造函数中分配实现.

有几种不同的方法可以做到这一点.

选项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.
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

如果你想保持你的构造函数最小,那么你可以将removeRow方法保留在类定义中,只需在构造函数中指定一个代理函数:

选项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.
    }

}
Run Code Online (Sandbox Code Playgroud)

选项3

最后,如果你使用像下划线或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.
    }

}
Run Code Online (Sandbox Code Playgroud)

然后你可以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!");
    });
}
Run Code Online (Sandbox Code Playgroud)

  • 我真的希望他们在TypeScript中改变这个功能,所以这总是在类的上下文中......如果有必要,可能还有其他一些方法可以获得调用者的上下文.但是,再一次,我通常不知道我在说什么. (3认同)
  • 哎呀,修好了.自我完成试图太有帮助我想:P (2认同)
  • 好吧,主要是我们必须跳过的语法障碍才能解决问题.如果TS提供了一些语言糖,使其更容易处理,我想我会很高兴.:) (2认同)

Zac*_*ris 29

更新:请参阅Sly的更新答案.它包含以下选项的改进版本.

另一个更新:泛型

有时您希望在函数签名中指定泛型类型,而不必在整个类中指定它.我花了几次尝试来弄清楚语法,所以我认为值得分享:

class MyClass {  //no type parameter necessary here

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

         //implementation here...

     }
}
Run Code Online (Sandbox Code Playgroud)

选项4

这里有几个语法可以添加到Sly_cardinal的答案中.这些示例将函数声明和实现保持在同一位置:

class Test {

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

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

      }

      constructor (){

      }
Run Code Online (Sandbox Code Playgroud)

}

要么

选项5

稍微紧凑,但放弃显式返回类型(如果不明确,编译器应该推断返回类型):

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 (){

      }
Run Code Online (Sandbox Code Playgroud)

}


Dmi*_*riy 5

使用 .bind() 在回调中保留上下文。

工作代码示例:

window.addEventListener(
  "resize",
  (()=>{this.retrieveDimensionsFromElement();}).bind(this)
)
Run Code Online (Sandbox Code Playgroud)

原始问题中的代码将变成这样:

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

它将回调函数内的上下文(this)设置为作为参数传递给绑定函数的任何内容,在这种情况下是原始的 this 对象。