Non-Static Extensions to JavaScript

In addition to static pre-runtime code checking TypeScript also features some runtime extensions to JavaScript. The most prominent of those are enums. Another important example is decorators.

Enums

What Are Enums?

Enums are sets of constant values which:

  • semantically document a programmer's intent, and

  • possibly enhance the code with some additional type checking features.

Numeric Enums

The simplest form of an enum in TypeScript is a numeric enum.

A numeric enum can be defined providing constant members without providing any explicit initialized values which are then implicitly initialized as numbers and auto‑incremented from 0.

enum Groceries {
  Bread,
  Oranges,
  Beverages
}

At runtime, it is possible to access an enum value using a member.

Groceries.Bread // => 0

It is also possible to define a numeric enum with some or all non-explicitly initialized numeric values which are initialized implicitly using auto‑incrementation.

enum Groceries {
  Bread = 1,
  Oranges = 3,
  Beverages
}

console.log(Groceries) // => {1: 'Bread', 3: 'Oranges', 4: 'Beverages', Bread: 1, Oranges: 3, Beverages: 4}

Numeric enums - in addition to the so-called forward mapping from members to numeric values and as opposed to all other enums - feature the so-called reverse mapping from numeric values to members.

Therefore, at runtime, it is possible to access the member using a numeric value.

Groceries[0] // => 'Bread'

String Enums

String enums are enums which values are string literals or other enum members with string values.

enum Horses {
  Geralt = 'Roach',
  Ciri = 'Kelpie',
  Alexander = 'Bucephalus',
  WitcherHorse = Geralt
}

console.log(Horses) // => {Geralt: 'Roach', Ciri: 'Kelpie', Alexander: 'Bucephalus', WitcherHorse: 'Roach'}

String enums do not feature reverse mapping from values to members.

Heterogenous Enums

Theoretically it is possible to create enums with both numeric and string literal initializers.

Enums With Computed Values

Member values do not have to be initialized explicitly as numeric or string literals. They can be computed as long as the computed value is a number.

// OK.
const getAnswerToEverything = () => 42

enum Answers {
  theAnswerToEverything = getAnswerToEverything()
}

console.log(Answers) // => {42: 'theAnswerToEverything', theAnswerToEverything: 42}

// Not OK.
const getGeraltHorse = () => 'Roach'

enum Horses {
  geralt = getGeraltHorse() // TS: Only numeric enums can have computed members, but this expression has type 'string'. If you do not need exhaustiveness checks, consider using an object literal instead.
}

Members with computed values cannot come before members with implicitly initialized values.

enum Answers {
  theAnswerToEverything = getAnswerToEverything(),
  someOtherAnswer // TS: Enum member must have initializer.
}

An enum can have members with values computed at runtime.

enum Numbers {
  Random = Math.random()
}

A constant enum expression is an enum that can be fully evaluated at compile time, i.e. before runtime.

Enums As Types

A literal enum member is an enum member with:

  • an implicitly initialized numeric value, or

  • an explicitly initialized literal numeric value, or

  • a pre-runtime computed numeric value, or

  • a literal string value.

An enum which only has literal enum members:

  • becomes a union type of all its members, and

  • has its members become types themselves.

enum WitcherProvenances {
  Geralt = 'Rivia',
  Ciri = 'Cintra'
}

enum SorceressProvenances {
  Yennefer = 'Vengerberg',
  Triss = 'Maribor'
}

function getWitcherProvenance(witcher: WitcherProvenances) {
  return witcher
}

getWitcherProvenance(WitcherProvenances.Geralt) // => Rivia
getWitcherProvenance(SorceressProvenances.Yennefer) // TS: Argument of type 'SorceressProvenances.Yennefer' is not assignable to parameter of type 'WitcherProvenances'.

Other Enums

In addition to the enum types presented above TypeScript also features:

  • const enums which are unavailable at runtime, and

  • ambient enums which describe shapes of other enums.

The characteristic of the const enums and ambient enums fall outside the scope of this tutorial.

Decorators

Decorators are declarations that:

  • annotate a class (constructor), class method, class accessor, class property or a class parameter,

  • evaluate to functions which are then called at runtime and meta‑programmatically change the said elements.

function foo(constructor: Function) {
  console.log(constructor) // ƒ Bar() {}
  // do something with the constructor function
}

@foo
class Bar {
  constructor() {}
}

Decorators are currently only a proposal for JavaScript and an experimental feature in TypeScript.

To enable decorators in TypeScript use the experimentalDecorators flag in tsconfig.json.

The characteristic of decorators falls outside the scope of this tutorial.