Learn TypeScript to Improve Your JavaScript
TypeScript is JavaScript for C# Developers. The CoffeeScript of .NETters. It is a superset of JavaScript that brings all the goodness of type-checking, classes, modules, lambda expressions and awesome tooling to the web front-end. Everything to make for a more productive and better JavaScript development experience.
Regardless of its many benefits, this brief article is not going to be a TypeScript tutorial nor a how-to. Instead, I will focus in the TypeScript feature that suprised me the most, the quality of the JavaScript generated by the TypeScript transcompiler and the transcompiler itself as a tool to learn JavaScript best practices.
Microsoft's TypeScript may be the best of the many JavaScript front ends. It seems to generate the most attractive code.
Before we start, note that you can try all article code samples yourself at the TypeScript playground as you read along, making modifications here and there to TypeScript and seeing how it reflects in JavaScript (notice also how the web-based text editor provides code autocompletion for you TypeScript, sweet!).
Let’s start with a class definition then:
// This is a TypeScript class definition
// It uses some syntactic sugar to declare and initialize public members
class Barbarian {
constructor(public name: string, public weapon: string) {}
attack() {
return (
this.name +
' enters a frenzy, wields his ' +
this.weapon +
' and strikes the enemy.'
)
}
}
var conan = new Barbarian('conan', 'bastard sword')
conan.attack()
// It produces this JavaScript
var Barbarian = (function() {
function Barbarian(name, weapon) {
this.name = name
this.weapon = weapon
}
Barbarian.prototype.attack = function() {
return (
this.name +
' enters a frenzy, wields his ' +
this.weapon +
' and strikes the enemy.'
)
}
return Barbarian
})()
var conan = new Barbarian('conan', 'bastard sword')
conan.attack()
As you can see in the example above, TypeScript does two interesting things when transcompiling classes:
- it produces an Immediately-Invoked Function Expression (IFFE pronounced iffy) that creates a new lexical scope to avoid polluting the global namespace.
- it defines the class methods in the Barbarian prototype, which basically means that all instances of Barbarian will reuse the same function (instead of each one its own function as it would have happened if we would have defined attack inside the Barbarian function).
A similar approach is used for modules:
// This is a TypeScript module definition
// It defines its API as exports
namespace Character {
export class Barbarian {
constructor(public name: string, public weapon: string) {}
attack() {
return (
this.name +
' enters a frenzy, wields his ' +
this.weapon +
' and strikes the enemy.'
)
}
}
}
var conan = new Character.Barbarian('conan', 'sword')
conan.attack
// It produces this JavaScript
var Character
;(function(Character) {
var Barbarian = (function() {
function Barbarian(name, weapon) {
this.name = name
this.weapon = weapon
}
Barbarian.prototype.attack = function() {
return (
this.name +
' enters a frenzy, wields his ' +
this.weapon +
' and strikes the enemy.'
)
}
return Barbarian
})()
Character.Barbarian = Barbarian
})(Character || (Character = {}))
var conan = new Character.Barbarian('conan', 'sword')
conan.attack
In this case we use another IFFE to wrap the class definition and add it to the Character namespace. The Character namespace is, in turn, initialized if it did not exist before by using the Character || (Character = {})
expression, a common JavaScript construct to initialize variables to a default value.
Finally, whichever are the TypeScript constructs that we use (and we’ll see a lot of them in the next example), the JavaScript that is generated is completely clean and pristine. See it with your own eyes:
// ****\*\*****
// TypeScript
// ****\*\*****
module Character {
export interface ICharacter {
name: string;
weapon: string;
hp: number;
attack(ICharacter): void;
}
export class Barbarian implements ICharacter {
hp: number;
constructor(public name: string, public weapon: string) {
this.hp = 100;
}
attack(enemy: ICharacter) {
console.log(this.name + " enters a frenzy, wields his "
+ this.weapon + " and strikes " + enemy.name);
}
attackMany(enemies: ICharacter[]) {
console.log("moooob attack!!!!");
enemies.forEach((e) => this.attack(e));
}
}
export class Mob<T extends ICharacter> {
theMob: ICharacter[];
constructor() {
this.theMob = [];
}
attack(enemy: ICharacter) {
this.theMob.forEach((c) => c.attack(enemy))
}
add(dude: ICharacter) {
this.theMob[this.theMob.length] = dude;
}
}
}
import Barbarian = Character.Barbarian;
import Mob = Character.Mob;
window.onload = () => {
var conan = new Barbarian("conan", "sword");
var krull = new Barbarian("krull", "axe");
conan.attack(krull);
var logenNinefingers = new Barbarian("Logen", "bastard sword");
var angryMob = new Mob<Barbarian>();
angryMob.add(conan);
angryMob.add(krull);
angryMob.attack(logenNinefingers);
};
// ****\*\*****
// JavaScript
// ****\*\*****
var Character;
(function (Character) {
var Barbarian = (function () {
function Barbarian(name, weapon) {
this.name = name;
this.weapon = weapon;
this.hp = 100;
}
Barbarian.prototype.attack = function (enemy) {
console.log(this.name + " enters a frenzy, wields his " + this.weapon + " and strikes " + enemy.name);
};
Barbarian.prototype.attackMany = function (enemies) {
var \_this = this;
console.log("moooob attack!!!!");
enemies.forEach(function (e) {
return \_this.attack(e);
});
};
return Barbarian;
})();
Character.Barbarian = Barbarian;
var Mob = (function () {
function Mob() {
this.theMob = [];
}
Mob.prototype.attack = function (enemy) {
this.theMob.forEach(function (c) {
return c.attack(enemy);
});
};
Mob.prototype.add = function (dude) {
this.theMob[this.theMob.length] = dude;
};
return Mob;
})();
Character.Mob = Mob;
})(Character || (Character = {}));
var Barbarian = Character.Barbarian;
var Mob = Character.Mob;
window.onload = function () {
var conan = new Barbarian("conan", "sword");
var krull = new Barbarian("krull", "axe");
conan.attack(krull);
var logenNinefingers = new Barbarian("Logen", "bastard sword");
var angryMob = new Mob();
angryMob.add(conan);
angryMob.add(krull);
angryMob.attack(logenNinefingers);
};
If you want to know more about TypeScript here’s a great talk by the Helsbergnator (hell yeah! XD) from this past Build 2013:
This post was inspired by Stefan Ingvarsson “Introduction to TypeScript” session on our local .NET User group. If you are a .NET developer near Linköping (Sweden), come and join us on facebook or at swenug.se!! :).
Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.