September 20, 2017
This is a cross-post from stackify.com
TypeScript (TS) and JavaScript are two widely known languages in the development world, but what are the differences and what use cases are better suited for one over the other? In this post, we’ll compare the two languages, how they relate to one another, discuss their primary differences, and outline the benefits of each.
JavaScript is a dynamic, weakly-typed scripting language built for the web. It enables developers to add interactive functionality to their web pages without requiring unnecessary server-side processing. During HTML parsing, script files are downloaded and executed within the user’s browser rather than on your application server.
TypeScript is an open source syntactic superset of JavaScript that uses a compiler to produce JavaScript (EcmaScript 3+). TypeScript offers type annotations which provide optional, static type checking at compile time. Since TypeScript is a superset of JavaScript, all JavaScript is syntactically valid TypeScript. However, that does not mean all JavaScript can actually be processed by the TypeScript compiler:
let a = 'a'
a = 1 // throws: error TS2322: Type '1' is not assignable to type 'string'.
TypeScript was created to “statically identify constructs that are likely to be errors”. This allows us to make safe assumptions about state during execution. Let’s compare the following JavaScript and TypeScript functions:
// JavaScript
function getPassword(clearTextPassword) {
if (clearTextPassword) {
return 'password'
}
return '********'
}
let password = getPassword('false') // "password"
Nothing in JavaScript will prevent a script from calling add(...)
with invalid parameters which will result in a silent error at runtime. This can be entirely avoided at compile time using TypeScript’s type annotations:
// TypeScript
function getPassword(clearTextPassword: boolean): string {
if (clearTextPassword) {
return 'password'
}
return '********'
}
let password = getPassword('false') // throws: error TS2345: Argument of type '"false"' is not assignable to parameter of type 'boolean'.
This contrived example demonstrates how we can prevent operations from acting on objects of an unexpected type. Historically, one of the biggest complaints of JavaScript was the difficulty in tracking down issues because of the lack of type checking combined with things like type coercion which may cause undesired results for those who aren’t familiar with JavaScript intricacies.
In addition to static type analysis, TypeScript also adds the following features to JavaScript:
Suppose the above getPassword(...)
function belonged to an external library. How would I, as a consumer of that library, know the type to pass into the function? There are jsdoc comments that many IDEs and editors, such as VSCode support. Then there is the library’s own documentation, which are tools like Dash make more accessible. But none of these provide the kind of experience offered by TypeScript out of the box.
Consider the fetch API as an example. The following screenshot demonstrates how we can explore the API using the VSCode Peek Definition feature. Using these tools we can quickly discover the input parameter types (RequestInfo
and RequestInit
) and the return type (Promise<Response>
). These kind of tools go well beyond what is available through classic JavaScript and jsdocs.
There are many misconceptions around why someone might choose TypeScript. I’ve cherry-picked a few to discuss here.
ES6 Features: One of the most common reasons for choosing TS is the desire to use ES6 features like modules, classes, arrow functions, and others. However, this is not a good reason for choosing TypeScript since the same thing can be achieved using Babel. In fact, it is not uncommon to see TypeScript and Babel used in the same application.
It’s Easier Than JavaScript: For the most part this statement is subjective but there are valid arguments that TS introduces syntax noise. The most important thing however, is that TS does not hide JS. It is not an excuse for disregarding JavaScript fundamentals. TS is still a superset of JavaScript and does not provide protection from many of the common complaints against JavaScript which are unrelated to the lack of static type checking (this, scopes, prototypes, etc.). Because of this, developers should still maintain strong JS competence.
Type Correctness == Program Correctness: While this may seem like an obviously incorrect statement, I believe static type checking provides an artificial safety net that developers can take for granted and is worth discussing. If type correctness does not imply program correctness, then what can we do to continually and repeatably ensure our program does what we intend? The best answer I’m aware of is unit testing. So, it begs the question that if we intend to prove our program is correct through unit tests, these tests should also prevent most type errors.
Static Typing Gives You Tree-Shaking:
Tree shaking refers to dead-code elimination through the use of static constructs like named module import
/export
and const
. TypeScript does not currently support tree-shaking out of the box.
It is common to hear developers choosing TypeScript because of features like modules and classes. However, it is important to understand these features are also available in JavaScript since ES6 and you can use Babel to transpile down to ES5 for greater browser compatibility. Because of this confusion, here is a quick syntax comparison for some of the more recent EcmaScript features. For each feature, you’ll find the TypeScript version and its compiled ES5 JavaScript along with the dynamic ES6 definition transpiled to ES5 using babel.
// -- TypeScript -- //
class Article {
name: string
constructor(name: string) {
this.name = name
}
}
// -- ES5 (using TS compiler) -- //
var Article = /** @class */ (function() {
function Article(name) {
this.name = name
}
return Article
})()
// -- ES6 -- //
class Article {
constructor(name) {
this.name = name
}
}
// -- ES5 (using babel) -- //
;('use strict')
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function')
}
}
var Article = function Article(name) {
_classCallCheck(this, Article)
this.name = name
}
// -- TypeScript -- //
export default class Article {}
// -- ES5 (using TS compiler) -- //
define(['require', 'exports'], function(require, exports) {
'use strict'
Object.defineProperty(exports, '__esModule', { value: true })
var Article = /** @class */ (function() {
function Article() {}
return Article
})()
exports.default = Article
})
// -- ES6 -- //
export default class Article {}
// -- ES5 (using babel) -- //
;('use strict')
Object.defineProperty(exports, '__esModule', {
value: true,
})
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function')
}
}
var Article = function Article() {
_classCallCheck(this, Article)
}
exports.default = Article
// -- TypeScript -- //
function log(message: string = null) {}
// -- ES5 (using TS compiler) -- //
function log(message) {
if (message === void 0) {
message = null
}
}
// -- ES6 -- //
function Log(message = null) {}
// -- ES5 (using babel) -- //
;('use strict')
function Log() {
var message =
arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null
}
Working with a New Library or Framework: Let’s suppose you’re taking up React for a new project. You are not familiar with React’s APIs, but since they offer type definitions, you can get intellisense that will help you navigate and discover the new interfaces.
Prefer Compile Time Type Checking: It is entirely possible to perform runtime type verification using vanilla JavaScript. However, this introduces additional runtime overhead that could be avoided by performing compile-time validation
Large Projects or Multiple Developers: TypeScript makes the most sense when working on large projects or you have several developers working together. Using TypeScript’s interfaces and access modifiers can be invaluable in communicating APIs (which members of a class are available for consumption).
So you’ve decided that it’s time to introduce a type system to your front-end development workflow. Is TypeScript the only option? The short answer is no. In fact there are two other major tools for type annotations which are worth consideration and have been discussed elsewhere.
My name is Jared Nance, I am an engineer for CloudWatch at Amazon Web Services in Seattle and previously Stackify in Kansas City. I enjoy building things and sharing what I learn along the way. You can follow me on Twitter or GitHub . Opinions are my own. I often explore technology vastly different from what I use at work and have no plans to blog about my work at AWS (at this time).