TypeScript Overview

Overview

  • Quick summary of the main features of TypeScript so that you can start work on programming projects.

  • Details will be covered in subsequent classes.

  • JavaScript versus TypeScript.

  • Primitive types.

  • Variables and declarations.

  • Arrays.

  • Objects.

  • Regular expressions.

  • Control statements summary.

  • Functions.

  • Classes.

  • Dynamic language.

  • TS literals vs JSON.

JavaScript vs TypeScript

  • JavaScript does not have any type declarations. It is type safe but all type enforcement is performed dynamically at runtime.

  • TypeScript is JavaScript along with optional type declarations. This allows static type enforcement before runtime.

  • If type is not provided, TypeScript infers a type if possible.

  • All legal JavaScript programs are legal TypeScript programs.

  • TypeScript programs are transpiled to JavaScript using the TypeScript compiler tsc.

TypeScript to JavaScript

Example TypeScript str-repeat.ts:

function strRepeat(str: string, repeat: number)
  : string[]
{
  return Array.from({length: repeat})
    .map((_, i) => str.repeat(i + 1));
}


globalThis.console.log(strRepeat('a', 4));

Compiled using tsc --lib es2022 --target es2022 str-repeat.ts to JavaScript str-repeat.js:

function strRepeat(str, repeat) {
    return Array.from({ length: repeat })
        .map((_, i) => str.repeat(i + 1));
}
globalThis.console.log(strRepeat('a', 4));

Can also be compiled to legacy JS using tsc --lib es2022 str-repeat.ts --outFile str-repeat-legacy.js to JavaScript str-repeat-legacy.js:

function strRepeat(str, repeat) {
    return Array.from({ length: repeat })
        .map(function (_, i) { return str.repeat(i + 1); });
}
globalThis.console.log(strRepeat('a', 4));

Run using node str-repeat.js to get output

[ 'a', 'aa', 'aaa', 'aaaa' ]

Types

  • JS has eight types (checked at runtime): undef, null, boolean, number, bigint, string, symbol and object.

  • TS has all JS types, plus additional types as well as ways of combining types to build more complex types.

  • TS has has two top types any and unknown. The former turns off all static type checking, whereas the latter forces the programmer to narrow to a more specific type before use.

  • TS has bottom type never. It can be used to represent the return type of a function which never returns (for example, it may abort or have an infinite loop).

  • TS uses the void type to represent the return type of a function which does not return a value.

  • TS also provides enum's (this is a TS feature which generates non-trivial JS code).

  • TS provides ways of combining types into more complex types like array and object types.

Numbers

All primitive numbers are floating point; no primitive integer types. TypeScript type number.

Examples under ts-node REPL (typed command ts-node).

> 1 + 3*2**2**3   //power ** high precedence, right assoc
769
> -5 % 3       //remainder; has sign of first operand
-2
> 3 << 2       //left shift equiv mult by power-of-2
12
> 15 >> 2      //right shift equiv divsn by power-of-2
3
> 0xf & 0x32   //hex literals; bitwise and
2
> 0xf | 0x32   //bitwise or
63
> (0xf ^ 0x32).toString(16)  //xor;
'3d'
> 1 << 31      //bitwise operators use 32-bit signed ints
-2147483648    //32-bit overflow
> 1n << 31n    //n suffix uses BigInt's.
2147483648n
> 

Strings

  • TypeScript type string.

  • Strings are immutable.

  • String literals can be enclosed within single quotes as 'hello' or double-quotes as "world". Absolutely equivalent; I prefer using 'string' as easier to type on my keyboard.

  • Above string literals can contain \-escape sequences. They cannot contain literal newlines but can contain escaped newlines.

  • + used for string concatenation.

  • Rich set of methods.

String Examples

> const str : string = 'hello\n\tworld'
undefined
> console.log(str)
hello
	world
undefined
let text = "echo a \\\n\t b c"  //no type declared
undefined
> .type text      //ts infers type from initializer
let text: string
undefined
> console.log(text)
echo a \
	 b c
undefined
> str + text
'hello\n\tworldecho a \\\n\t b c'

Template String Literals

  • Enclosed within backquotes.

  • Can contain literal newlines.

        > `Mult-line
        ... string`
        'Mult-line\nstring'
    
  • Allows interpolating arbitrary expressions enclosed within ${...}.

        > `The circumference of a unit circle is
        ... ${2*Math.PI*1}\n`
        'The circumference of a unit circle is\n6.283185307179586\n'
        >
    

Variables and Declarations

  • In JavaScript, it is possible to use a variable without declaring it. Do not do so; this is a bad idea and creates a "global " variable. Modern JS environments disallow this.

  • Do not use legacy var declarations; surprising semantics.

  • Modern code declares block-scoped variables using const or let. Less surprising semantics.

  • Always try to initialize variable in declaration. When possible, prefer const to let.

  • const means variable cannot be changed. However, it is possible that the object referenced by the variable can still be changed.

Variables and Declarations: Examples

Using ts-node REPL:

> let a: number = 22
undefined
> let a : number;
let a : number;
... Cannot redeclare block-scoped variable 'a'.
...
> const b : number
... 'const' declarations must be initialized.
...
> const b : number = 33;
const b : number = 33;
undefined
let [c, d, e] = [88, 22, 15/3] //destructuring decl
undefined
> .type c  //inferred type
let c: number
> [a, b, c, d, e]
[ 22, 33, 88, 22, 5 ]

TypeScript Arrays

Array literals are comma-separated expressions within square brackets; last item can optionally be followed by a comma.

TypeScript arrays must be homogeneous (all elements must be of the declared or inferred type). Type is specified as T[] where T is the type of the elements.

> const arr = [1, 2, 'hello'.length, ]
undefined
> .type arr
const arr: number[]
> arr
[ 1, 2, 5 ]
> arr[1]
2
> arr.length
3
> arr[0] = 'world'
... Type 'string' is not assignable to type 'number'.
...
>

Array Methods

MDN

> const arr1 : number[] = [1, 2, 'hello'.length, 6, ];
undefined
> arr1.slice(1, 3) //args: incl, excl
[ 2, 5 ]
> arr1.slice(1) //start to end
[ 2, 5, 6 ]
> arr1.slice(1, -1)  //neg index counts from end
[ 2, 5 ]
> arr1.slice(-2)  //start with -2 to end
[ 5, 6 ]
> arr1.indexOf(5)
2
> arr1.indexOf(8)
-1
> arr1.push(7, 8) //destructive operation
6
> arr1.indexOf(8)
5
> arr1
[ 1, 2, 5, 6, 7, 8 ]
> 

Array Methods Continued

> arr1.shift()   //destructive
1
> arr1
[ 2, 5, 6, 7, 8 ]
> arr1.at(-1)
8
> arr1.pop()     //destructive
8
> arr1
[ 2, 5, 6, 7 ]
// unshift and push both destructive
> arr1.unshift(22); arr1.push(-1)
6
> arr1
[ 22, 2, 5, 6, 7, -1 ]

Array Methods Continued

> arr1.sort()         //destructive
[ -1, 2, 22, 5, 6, 7 ]
> arr1.sort((a, b) => a - b)
[ -1, 2, 5, 6, 7, 22 ]
> arr1
[ -1, 2, 5, 6, 7, 22 ]
> arr1.concat(33, 44) //non-destructive
[
  -1,  2,  5,  6,
   7, 22, 33, 44
]
> arr1
[ -1, 2, 5, 6, 7, 22 ]
> 

Objects

JS objects are maps of String properties to arbitrary values, with OO pixie dust sprinkled in.

JS objects can be used in two ways:

  • As C structures (aka records) like { x: 22, y: 33, z: 44 } to represent a 3D point with fields x, y and z.

  • As maps to map string keys to values (JS also has a specific Map type for this use case, but objects have been used traditionally).

TS allows both uses.

Objects as Records

> let o1 = { a1: 22, b1: 33, c1: 44 } 
undefined
> .type o1  //inferred type
let o1: {
    a1: number;
    b1: number;
    c1: number;
}
> o1.b1
33
> o1.d1
... Property 'd1' does not exist on type
...
o1 = { a1: 44, b1: 88 }
... Property 'c1' is missing in type
...

Objects as Records with Optional Fields

> let o2 : { a2: number, b2?: number } //b2 optional
undefined
> .type o2
let o2: {
    a2: number;
    b2?: number | undefined;
}
> o2 = { a2: 12, b2: 66 }
{ a2: 12, b2: 66 }
> o2 = { a2: 8 }
{ a2: 8 }
> o2 = { }
... Property 'a2' is missing in type '{}'...
...

Objects as Maps

//declare m1 to be a map from string keys to any type
> let m1 : { [index: string]: any } = {}
undefined
> m1.a1 = 33
33
> m1['b' + '1'] = 44 //[ ] can contain expr
44
> m1
{ a1: 33, b1: 44 }
> m1.c1 = 55
55
> m1
{ a1: 33, b1: 44, c1: 55 }
> m1.d1 = 'hello'  //type allows any value
'hello'
> m1
{ a1: 33, b1: 44, c1: 55, d1: 'hello' }
> let [a1, b1 ] = [22, 33] 
undefined
> m1 = { a1, b1, d1: 99}  //shorthand literal notation
{ a1: 22, b1: 33, d1: 99 }
> 

Object Methods

MDN

> let obj =
    Object.assign({}, {a: 22, b: 33}, {a: 44, c: 99})
undefined
> obj
{ a: 44, b: 33, c: 99 }
> Object.keys(obj)
[ 'a', 'b', 'c' ]
> Object.values(obj)
[ 44, 33, 99 ]
> Object.entries(obj)
[ [ 'a', 44 ], [ 'b', 33 ], [ 'c', 99 ] ]
> Object.fromEntries(Object.entries(obj).slice(1))
{ b: 33, c: 99 }
> 

Regular Expressions

Invaluable tool for string matching, JS and TS support regex literals enclosed within /.../.

> const [var1, var2] = [3, 5]
> let str = `var1*var2 = ${var1*var2}`
> str
'var1*var2 = 15'
> str.match(/=/)        //first match for '='
[ '=', index: 10, ... ] //at index 10
> str.match(/ /)        //look for space
[ ' ', index: 9, ... ]  //at index 9
> str.match(/\s/)       //special regex for whitespace
[ ' ', index: 9, ... ]
> str.match(/[0-9]/)    //char class: any char '0'...'9'
                        //match single char in class
[ '1', index: 3, ... ]
> str.match(/\w/)       //word char: [a-zA-Z0-9_]
[ 'v', index: 0, ... ]
> str.match(/[A-Z]/)    //match uppercase char
null                    //fails

Regular Expressions Continued

> str.match(/[a-z]/)   //match lowercase letters
[ 'v', index: 0, ... ]
> str.match(/[a-z]+/) //+ means one-or-more 
[ 'var', index: 0, ... ]
> str.match(/\w+/)  //one-or-more word chars
[ 'var1', index: 0, ... ]
> str.match(/\d+/)  //one-or-more digits
[ '1', index: 3, ... ]
> let m = str.match(/(\w+)\W+(\w+)/) //capturing paren
> m
[ 'var1*var2','var1', 'var2', index: 0, ... ]
> [m[0], m[1], m[2]]  //m[1], m[2] captured text
[ 'var1*var2', 'var1', 'var2' ]

Control Statements

  • C-style if-else, for, while, do-while, switch-case statements.

  • For iterating through an array always use for-of.

        > const arr = [33, 53, 36];
        > for (const a of arr) { console.log(a); }
        33
        53
        36
        //.entries() produces [index, element] pairs
        > for (const [i, a] of arr.entries()) {
            console.log(a*i);
          }
        0
        53
        72
    

Functions

  • Traditional functions. Example factorial:

        > function fact(n: number): number {
        ... return (n < 1) ? 1 : n*fact(n-1);
        ... }
        > fact(4)
        24
    
  • Anonymous functions stored in a variable:

        > let add = function (a: number, b: number) {
        ... return a + b ;
        ... }
        undefined
        > add(3, 4)
        7
        > .type add
        let add: (a: number, b: number) => number
    
  • Fat arrow anonymous functions:

        > const mult = (a: number, b: number ) => a*b
        undefined
        > mult(6, 7)
        42
    
  • Subtle semantic differences between anonymous functions defined using function and those defined using fat-arrow.

Variable Args

If last formal parameter is preceeded by ..., then all remaining arguments are collected into that rest parameter as an array.

> function polyEval(x: number, ...coeffs: number[]) {
    let pow = 1;
    let sum = 0;
    for (const coeff of coeffs) {
      sum += pow*coeff;
      pow *= x;
    }
    return sum;
  }
> polyEval(2, 1, 2, 3, 4)
49
> 1*2**0 + 2*2**1 + 3*2**2 + 4*2**3
49

First-Class Functions

Functions are first-class values; i.e., they can be treated like any other values:

  • Can be stored in data-structures.

        > let fns = [ add, mult, ];
        > [ fns[0](2, 3), fns[1](5, 6) ]
        [ 5, 30 ]
    
  • Can be passed and returned from functions.

        //type declares a type alias
        > type Fn2 = (a: number, b: number) => number
        > const fn =
            (cond: boolean, fn1: Fn2, fn2: Fn2) =>
    	  cond ? fn1 : fn2;
        > fn(false, add, mult)(3, 6)
        18
        > fn(true, add, mult)(3, 6)
        9
    

Classes

JS does not really have classes in the sense of other OO languages. Syntactic sugar introduced relatively recently.

Does not work under ts-node. On TS Playground

> class Point2 {
    x: number;   //public instance field
    y: number; 
    constructor(x: number, y: number) {
      this.x = x; this.y = y;
    }
    dist0() : number { //method
      return Math.sqrt(this.x**2 + this.y**2);
    }
  }
const p = new Point2(3, 4);
console.log(p)    // Point2 { "x": 3, "y": 4}
console.log(p.dist0()) // 5

First Class Classes

Classes are merely syntactic sugar for functions; hence they too are first-class.

TS Playground

class Name {  
  #first: string;        //# indicates JS private
  private last: string;  //TS private keyword
  constructor(first: string, last: string) {
    this.#first = first; this.last = last;
  }
  fullName() {
    return `${this.#first} ${this.last}`;
  }
};

const obj = {
  point: Point2,
  name: Name;
}

First Class Classes Continued

const p1 = new obj.point(12, 5)
console.log(p1.dist0()) // 13
const n = new obj.name('bill', 'smith');
console.log(n.fullName()) // "bill smith"

JavaScript Literals

JavaScript has very rich notation for literal data:

A primitive JS literal value is one of:

  • An integer (including 0xAf and octal 072 literals), floating point literal like 1.23e-2 or 1E4.

  • Special values like null, undefined or NaN.

  • A string literal delimited by single or double-quotes or by backquotes for template literals.

  • Anonymous functions using either the function keyword, or fat-arrow.

  • Anonymous classes.

JavaScript Literals Continued

A compound JS literal value is one of:

  • Arrays of comma-separated literal data enclosed within [ ].

  • Object literals consisting of comma-separated key: value pairs enclosed within { }.

    • Keys can be specified as non-template strings or simple identifiers.

    • Dynamic key values can be specified within [ ].

    • If a key-value is simply the name of a variable var, then it is equivalent to the key-value var: var.

    • If a key-value looks like id(...) { ... }, then it is equivalent to id: function (...) { ... }.

Both object and array literals permit an optional trailing , after the last item.

Complex JavaScript Literal Example

Using node REPL:

> const [ n1, n2 ] = [ 42, 33 ];
> const [ str1, str2 ] = [ 'hello', 'world' ];
> { n1: n1,
    n2,
    [`${str1}_${str2}`]: (a, b) => a+b,
    [ str1 + '-' + str2 ]: 99,
    mult(x, y) { return x*y; },
    arr: [ n1, n2, ],
  }
{
  n1: 42,
  n2: 33,
  hello_world: [Function: hello_world],
  'hello-world': 99,
  mult: [Function: mult],
  arr: [ 42, 33 ]
}
> 

JSON

JavaScript Object Notation: Very simple specification. A JSON literal can be one of:

  • A primitive literal can be a number, string within double quotes, or true, false or null.

  • An array literal consists of JSON literal values separated by comma's. An optional trailing comma is not allowed.

  • An object literal consists of key: value pairs separated by comma's. An optional trailing comma is not allowed. The key must be a string within double-quotes.

JSON Example

{
  "john": {
     "name": "John Cassidy",
     "age": 42,
     "kids": [ "jane", "bill" ]
   },
  "mary": {
     "name": "Mary Jones",
     "age": 42,
     "kids": [ "lucy", "sue" ]
   }
}

JSON Evaluation

  • Can be easily parsed by standard JS library using JSON.parse().

  • A JS object can easily be converted to a JSON string using JSON.stringify(). Usually, JS objects without a JSON representation are silently ignored or represented as a null when within an array.

  • Excellent data format for exchanging structured data between heterogeneous systems.

  • Restricted format.

  • Comments are not allowed. Makes it a bad choice as a configuration format (unfortunately, many JS tools use it as a configuration format; some of them allow comments).