In-Depth

A TypeScript Primer

Whether you're new to JavaScript or well-versed in all it has to offer, TypeScript is a compelling option.

TypeScript was developed to capitalize on the strengths of JavaScript while simultaneously shoring up its weaknesses. JavaScript is likely the most widely used programming language in the world, and at the same time, possibly the most disliked. Although almost 20 years old, it has some of the most-advanced language features available even today, while still harboring characteristics that were recognized as flaws long before JavaScript was born.

This article dives into the details of TypeScript and its features, examining everything from the tooling to the specific syntax that it offers. Before I can do that, however, it's necessary to examine the "why" behind TypeScript. In other words, which JavaScript characteristics does TypeScript set out to reuse, and which ones does it try to curb?

JavaScript: The Good

Most important, JavaScript offers ubiquity. It provides a single development platform that executes on virtually all modern computers -- whether from mobile devices, tablets or high-end servers. This single feature alone is the key to the vast success of JavaScript and its selection over other languages that, from a language design perspective, are significantly better.

Although the best JavaScript feature is its ubiquity, that isn't the only thing in its favor. One of the more advanced features of JavaScript is support for function expressions; this has proven to be the key characteristic, enabling it to achieve some semblance of object-oriented programming (OOP). Through function expressions, JavaScript is able to support constructs such as classes, encapsulation and inheritance, to name a few.

Another critical language feature required for OOP is closure, because the combination of closure and function expressions allows data to be grouped with behavior. By definition, closure allows the referencing of variables defined in one scope from within the context of a second scope, such as a reference to a local variable from within a function expression (the equivalent of an anonymous method or lambda expression in the Microsoft .NET Framework). By nesting data and function expressions within a parent function expression, a JavaScript "class" is defined -- one that might even include encapsulation.

Another compelling characteristic is the vibrancy of the development community and the frameworks being created. Open source libraries such as Knockout, jQuery and Modernizr, to name just a few, provide significant enhancements to JavaScript. This community has had a tremendous influence on the establishment of JavaScript as a mainstream language.

JavaScript: The Bad

Along with its good points, few would deny that the huge popularity of JavaScript is not a result of its impressive set of language features.

Object-Oriented via Discipline

To start with, even the basics of object-oriented constructs are lacking. While such constructs certainly exist, they're not intrinsic patterns of JavaScript; instead, they're the result of disciplined structure that developers impose on the language. This is true for basic constructs such as classes, and even more so for encapsulation and inheritance. They weren't intentionally placed through language design, but imposed after the fact.

Much of the object-oriented structure of JavaScript is achieved through the reuse of function expressions and the encapsulation of such expressions within other (containing) function expressions. Consider, for example, providing disambiguating types via the use of a namespace. In JavaScript, this is accomplished by embedding within an outer function expression (representing the namespace) another function representing a class. Then, to declare a member function, a prototype definition is embedded within the function that represents the class. The definition of Person in Listing 1 provides an example.

Listing 1. Person definition.

var TypeScriptingStuff; (function (TypeScriptingStuff) { var Person = (function () { function Person(FirstName, LastName) { this.FirstName = FirstName; this.LastName = LastName; } Person.prototype.getFullName = function () { return this.FirstName + " " + this.LastName; }; return Person; })(); TypeScriptingStuff.Person = Person; })(TypeScriptingStuff || (TypeScriptingStuff = {}));

Supporting a namespace-qualified simple class (one that supports two member variables, one member function and a constructor) goes beyond what would typically be considered basic syntax. It isn't unmanageable on its own, but using the same syntax for an enterprise library of types is less than efficient. The core problem is all the "ceremony" that the language requires in order to enable object-oriented features.

Dynamic No Matter What

Arguably, the main criticism of JavaScript comes from strong-typing "bigots" who celebrate the fact that when they mistakenly code against a nonexistent API, they receive a compile warning even before they execute any code.

This doesn't mean that dynamic support shouldn't be available; even strongly typed languages like C# support dynamic typing, and for good reason. But JavaScript dynamic support is the only option, whereas C# support is an opt-in paradigm.

In contrast, JavaScript requires dynamic typing. The result is that simple typos don't appear until runtime (hopefully in unit tests). Simply using the incorrect case, such as "Id" rather than "ID" or "id," will go unnoticed until the code is executed. Problems like this become especially problematic (and common) when using libraries such as Knockout, as they fundamentally change how a type is defined. Consider the bug in the Knockout observable type representing a Task in this code:

var TaskModel = (function () { function TaskModel(description, completed) { this.Description = ""; this.Completed = ko.observable(completed); } return TaskModel; })();

The intention of this code would be for all members to be observable, so that changes would automatically be rendered in the UI. Unfortunately, without type declaration and then type checking, there's no way to implicitly flag this code's problem -- that assigning an empty string to Description (rather than a ko.observable type value, as was assigned to Completed) results in a property that's unobservable. More generally, mismatched type assignments are not implicitly discoverable in loosely typed languages like JavaScript without executing the code -- delaying bug discovery far later in the development cycle than is desirable.

Admittedly, strong typing is more verbose, and detractors would argue the additional ceremony is pointless. Its value rests on two premises:

The closer to code authoring (ideally inline, as it's typed) that a bug is identified, the better. Any implicit identification of bugs (without explicit action such as writing unit tests) is more valuable than runtime mechanisms. (By no means is this an excuse to avoid writing unit tests. Rather, strong typing results in more robust and correct code before you even get to executing the unit tests -- eliminating a host of issues that would otherwise have to be diagnosed.)

Idiosyncratic

There are numerous other idiosyncrasies in the JavaScript language. Table 1 lists of some of these quirks.

Global variables allowed, implied (such as when a variable is not declared), and even required document.write( "<"h1>Hello! My name is Inigo Montoya.</h1>"); Implicit semicolon insertion, sometimes function () { var func1 = function() { return { }; } var func2 = function() { return { } }; expect(func1() == func2()).toBe(false); } == does type coercion before comparison, resulting in seemingly random transitivity function () { expect(0 == '0').toBe(true); expect(0 == '').toBe(true); expect('' == '0').toBe(false); expect(false == 'false').toBe(false); expect(false == '0').toBe(true); expect(" \t\r

" == 0).toBe(true); expect(null == undefined).toBe(true); } Mathematics isn't accurate function () { expect(0.1 + 0.2).toBe(0.30000000000000004); } Table 1. JavaScript idiosyncrasies.

By no means is Table 1 comprehensive; it just points out some of the more-common gotchas that inattentive programmers might encounter.

TypeScript to the Rescue

TypeScript was created to preserve the flavor of JavaScript and keep the good parts, while addressing a significant number of the bad parts. It's the combination of a prebuild lint tool type and a language specification. The prebuild lint tool portion executes against an enhanced JavaScript-like syntax -- the TypeScript language -- to enable both strong typing and inherent object-oriented constructs at compile time.

The preservation of JavaScript is maintained in essentially two ways. First, the TypeScript compiler, tsc.exe, compiles a TypeScript file (*.ts by convention) into a JavaScript file (*.js). In other words, the output of a successful TypeScript file is an ECMA3- (default) or ECMA5-compliant JavaScript file. As such, the TypeScript essentially achieves the same ubiquity of JavaScript -- the compiler-produced .js files will be compatible with any and all modern browsers. This is critical, because it means there's no requirement that Internet Explorer (or any other browser, for that matter) be updated to support TypeScript. This isn't a sinister plot on Microsoft's part to lock the world into using its browser; on the contrary, Microsoft has intentionally supported all browser platforms since the inception of TypeScript. Furthermore, the source code for the entire TypeScript implementation is available under an open source Apache License 2.0, allowing developers to update and improve the source code or even fork the code to enhance the tooling.

The second way the TypeScript compiler preserves JavaScript is by allowing existing JavaScript to remain intact, rather than forcing the porting of it over to TypeScript. You can take an existing block of JavaScript code and inject it into a TypeScript file, only to have the JavaScript extracted and embedded into a .js file. In fact, in order to stay true to JavaScript, the TypeScript developers targeted compatibility with ECMAScript 6 in terms of classes, modules and arrow functions. These factors demonstrate that the goal of TypeScript is to provide a better JavaScript, while still strongly preserving the feel of JavaScript.

As mentioned, the two key features TypeScript brings to JavaScript are strong typing and inherent object-oriented constructs. These are important because wherever possible, the compiler should be verifying intent rather than waiting until a developer writes some unit test to discover an issue; or, even worse, the bug gets distributed and discovered by an end-user instead.

TypeScript Tooling

There are essentially two main distribution mechanisms for TypeScript tooling:

As a Node.js package. As a Visual Studio 2012 plug-in, leveraging the command-line compiler tsc.exe.

As a plug-in, it's fully integrated into the Visual Studio IDE, offering IntelliSense, a project and file wizard, continuous compilation/type checking, debugging and even refactoring support for rename (see Figure 1).

[Click on image for larger view.] Figure 1. TypeScript supports rename refactoring.

The TypeScript compiler integrates smoothly into the build process by running as a build step immediately prior to the execution of additional compilation steps such as the execution of a minifier or even an additional lint compatibility checker. Essentially, the TypeScript compiler takes the *.ts input file and outputs *.js files against which you can continue to execute any other additional tooling you may want.

Object-Oriented Constructs

For an overview of the object-oriented characteristics of TypeScript, consider Listing 2.

Listing 2. Declaring a Class in TypeScript.

// Module (namespace equivalent) module TypeScriptingStuff { // Class export class Person { // Constructor constructor(public FirstName: string, public LastName: string) { }; // Instance function member public getFullName(): string { return this.FirstName + " " + this.LastName; }; } }

This code is the exact TypeScript equivalent of the JavaScript shown in Listing 1. One method for evaluating TypeScript is to simply ask the question of whether it would be easier to write, understand, and maintain the TypeScript code or the JavaScript equivalent.

The appeal of TypeScript (especially for .NET developers) is the native TypeScript support for object-oriented concepts: modules (or namespaces), classes, constructors, properties (the equivalent of a C# field), and access modifiers such as public and private. Each of these is relatively intuitive from the listing -- except, perhaps, support for properties. Property support is less intuitive: for example, Listing 2 uses shorthand for the properties set by the constructor. Rather than explicitly assigning this.FirstName and this.LastName (as in JavaScript no field declaration is necessary, just assignment), the implementation is implied via the parameter modifier public. The equivalent explicit implementation is shown in Listing 3.