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 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
.
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' ]
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.
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 >
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.
> 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'
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' >
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.
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 ]
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'. ... >
> 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 ] >
> 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 ]
> 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 ] >
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.
> 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 ...
> 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 '{}'... ...
//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 } >
> 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 } >
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
> 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' ]
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
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.
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
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
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
Classes are merely syntactic sugar for functions; hence they too are first-class.
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; }
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 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.
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.
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 ] } >
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.
{ "john": { "name": "John Cassidy", "age": 42, "kids": [ "jane", "bill" ] }, "mary": { "name": "Mary Jones", "age": 42, "kids": [ "lucy", "sue" ] } }
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).