Along with support for standard function expressions that use the function keyword, TypeScript also introduces a concept of arrow functions. Interestingly this feature is most likely to be included in the next version of JavaScript - ECMAScript 6. Arrow functions introduce a more compact way of defining functions, but also have been designed to work particularly well with callbacks.
This is an introductory post that shows examples of arrow functions usage in TypeScript and explains how they differ from standard function expressions in terms of this keyword binding.
If you are familiar with C#, arrow functions have similar look and feel to lambda expressions. Let's start by creating a simple TypeScript function that calculates sum earned by deposited funds.
var calculateInterest = function (amount, interestRate, duration) { return amount * interestRate * duration / 12; }
Using arrow function expression we can define this function alternatively as follows:
var calculateInterest2 = (amount, interestRat, duration) => { return amount * interestRate * duration / 12; }
We can simplify this further by using assignment expression instead of function body block:
var calculateInterest3 = (amount, interestRate, duration) => amount * interestRate * duration / 12;
This is a compact form that saves us some typing. It is worth mentioning that all three TypeScript functions will compile to identical JavaScript code.
var calculateInterest = function (amount, interestRate, duration) { return amount * interestRate * duration / 12; }; var calculateInterest2 = function (amount, interestRate, duration) { return amount * interestRate * duration / 12; }; var calculateInterest3 = function (amount, interestRate, duration) { return amount * interestRate * duration / 12; };
As you possibly know TypeScript provides a way of describing object contracts in the form of interfaces. Arrow expressions can be used to define function properties of the object.
interface BankAccount { balance: number; withdraw: (amount: number) => void; deposit: (amount: number) => void; calculateInterest(interestRate: number, duration: number) : number; }
Just like standard function expressions, arrow functions support optional and default parameters.
var executeStandingOrder = function (amount: number = 100, description?: string) { var message = 'Standing order amount = $' + amount; console.log(message); } var executeStandingOrder2 = (amount: number = 100, description?: string) => { var message = 'Standing order amount = $' + amount; console.log(message); }
This will result in identical JavaScript being generated for both functions.
var executeStandingOrder = function (amount, description) { if (typeof amount === "undefined") { amount = 100; } var message = 'Standing order amount = $' + amount; console.log(message); };
At this point you may think that standard and arrow functions are pretty much the same and can always be used interchangeably. This is not true, as there is one subtle but very important difference between the two.
Consider the following implementation of BankAccount interface.
var account: BankAccount = { balance: 2020, withdraw: (amount: number) => { this.balance -= amount; }, deposit: function (amount: number) { this.balance += amount; }, calculateInterest: function (interestRate: number, duration: number) { return this.balance * interestRate * duration / 12; } }
Now let's deposit and withdraw some funds.
account.deposit(4000); account.withdraw(2000); console.log('Balance = $' + account.balance);
If I got my math right, the resulting balance after the operations should be $4020, but when we execute this code (I am using jQuery to execute after DOM has been loaded) we will see the following output.
Balance = $6020
Well, clearly something is wrong with our logic and even worse there is no console error that would point us in a right direction. If you have some JavaScript experience, you may immediately suspect 'this' keyword usage as a potential culprit. And you are right.
Standard functions (ie. written using function keyword) will dynamically bind this depending on execution context (just like in JavaScript), arrow functions on the other hand will preserve this of enclosing context. This is a conscious design decision as arrow functions in ECMAScript 6 are meant to address some problems associated with dynamically bound this (eg. using function invocation pattern).
To clarify what it means in practice, let's look at generated JavaScript:
var _this = this; var account = { balance: 2020, withdraw: function (amount) { _this.balance -= amount; }, deposit: function (amount) { this.balance += amount; } };
As you can see for withdraw arrow function TypeScript uses var that=this pattern/trick to preserve this of enclosing context (which is a global object = window in this scenario). In other words we are trying to decrease balance property of a global object (window.balance = window.balance - amount). Because window.balance is undefined we end up assigning NaN to the property and no error is being thrown (another nice 'feature' of the language ;)).
Deposit on the other hand is defined using standard function expression, this means that if we invoke it as a method (ie. account.deposit(4000)), this will point to account object and everything will work as expected.
Aforementioned behavior of arrow functions can be particularly useful when dealing with callbacks. Let's add following method to the interface and its implementation:
interface BankAccount { balance: number; //(...) standingOrder: (amount:number) => void; } var account: BankAccount = { balance: 2020, //... standingOrder: function (amount: number) { setTimeout(() => { this.balance -= amount; console.log('Standing order executed. Current balance is $' + this.balance); }, 700); } }
In the example above we are invoking setTimeout function that accepts a callback to be executed after a given delay. If we used standard function statement as the callback, then this would be bound to a global object and the code wouldn't work as expected. However, because we are using arrow function this will be preserved from enclosing context and will point to account object as intended.