Understanding TypeScript Code Generation, Runtime Behavior, and Modern JavaScript Features

TypeScript code generation

TypeScript Compiler: Two Jobs, One Goal: checking for type errors and compiling to JavaScript.

In the TypeScript realm, the compiler has two main tasks:

  1. Error Checker: It acts like a detective, finding and fixing type errors in your code.
  2. JavaScript Converter: It transforms your TypeScript into JavaScript, so your code can run in any JavaScript environment.

These two processes are independent of each other. Types do not affect the execution of the code in a JavaScript engine, as they are completely erased during compilation. TypeScript can still output JavaScript even in the presence of type errors. Here is an example of TypeScript code with a type error:

const add = (a: number, b: number): number => a + b;
const result = add('x', 'y'); // Argument of type 'string' is not assignable to parameter of type 'number'.

However, it can still produce executable JavaScript output:

'use strict';
const add = (a, b) => a + b;
const result = add('x', 'y'); // xy

You can’t inspect TypeScript types during runtime. For example:

interface Animal {
name: string;
}
interface Dog extends Animal {
bark: () => void;
}
interface Cat extends Animal {
meow: () => void;
}
const makeNoise = (animal: Animal) => {
if (animal instanceof Dog) {
// 'Dog' only refers to a type, but is being used as a value here.
// ...
}
};

TypeScript types disappear after compilation, running this code in JavaScript isn’t possible. To detect types at runtime, we use other methods, like TypeScript’s ‘tagged union’ feature. For example:

interface Dog {
kind: 'dog'; // Tagged union
bark: () => void;
}
interface Cat {
kind: 'cat'; // Tagged union
meow: () => void;
}
type Animal = Dog | Cat;

const makeNoise = (animal: Animal) => {
if (animal.kind === 'dog') {
animal.bark();
} else {
animal.meow();
}
};

const dog: Dog = {
kind: 'dog',
bark: () => console.log('bark'),
};
makeNoise(dog);

The ‘kind’ property serves as a runtime value for distinguishing objects in JavaScript..

It is also possible for a value at runtime to have a type different from the one declared in the type declaration.

For example, if a developer misinterprets an API type and adds incorrect annotations. TypeScript, being an extension of JavaScript, allows us to use the ‘class’ keyword both as a type and as an actual value in runtime.

class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
constructor(public name: string, public bark: () => void) {
super(name);
}
}
class Cat extends Animal {
constructor(public name: string, public meow: () => void) {
super(name);
}
}
type Mammal = Dog | Cat;

const makeNoise = (mammal: Mammal) => {
if (mammal instanceof Dog) {
mammal.bark();
} else {
mammal.meow();
}
};

const dog = new Dog('Fido', () => console.log('bark'));
makeNoise(dog);

In JavaScript, when you have a ‘class,’ it comes with a ‘prototype’ property. You can use the ‘instanceof’ operator to check if this ‘prototype’ property of a constructor is anywhere in the object’s ‘prototype’ chain.

Now, the cool thing about TypeScript is that it doesn’t impact how fast your code runs because it erases all the type information during compilation. However, it does add some extra work during the build process.

Modern JavaScript Now (Downleveling)

Speaking of TypeScript’s superpowers, it can take your fancy, modern JavaScript code and turn it into an older, more universally compatible version. We call this ‘downleveling.’ This way, you get to enjoy the latest JavaScript features while still being friends with older systems.

TypeScript translates your code into older JavaScript versions, it might make it run a tad slower than the original. Now, here are some cool modern JavaScript features that TypeScript supports:

  • We’ve got ECMAScript modules, which are a cleaner way to organize code.
  • Instead of getting tangled in prototypes, we have the user-friendly ‘class.’
  • Forget about ‘var’; we’ve got ‘let’ and ‘const’ for safer variable handling.
  • And when it comes to loops, ‘for-of’ and ‘.forEach’ are the modern heroes, replacing the old-school ‘for’ loop.”
  • Arrow functions instead of function expressions.
  • Destructuring assignment.
  • Shorthand property/method names and computed property names.
  • Default function parameters.

Leave a Reply

Your email address will not be published. Required fields are marked *