var
versus let
versus const
Rule(s)
- JavaScript is known both as a permissive and powerful language. Skilled developers tend to write compact, but, sometimes, unreadable statements. In this context, it is critical to understand how the
if
control statement operates.Example
if(expression) {} // This evaluates to 'true' if 'expression' is not: null undefined NaN "" // Empty string 0 false
Rule(s)
- JavaScript operators, notably
&&
and||
, are concerned with other types thanboolean
(see also here…).Example
// 'A || B' returns the value 'A' if 'A' can be coerced into 'true'; it returns 'B' otherwise... const port = process.env['PORT'] || 8080; // Alternative: const port = process.env['PORT'] ? process.env['PORT'] : 8080;
Rule(s)
- Available types in TypeScript are (JavaScript) primitive types (e.g.,
boolean
ornumber
) plus platform-independent native types (e.g.,Date
,Error
,RegExp
,Object
,Event
, orFunction
).- TypeScript supports generics that reinforce type coercion, e.g.,
Array<E>
,Map<K,V>
, orPromise<T>
.- The Document Object Model (DOM) Application Programming Interface -API- is straightforwardly available through the
Document
(here…),HTMLElement
… types.- Node.js-related types require specific installation as does any TypeScript library that aims at providing its own types' definition.
Example Base.ts.zip
const re: RegExp = /^[A-Z]{3}$/; // 'RegExp' native type... let bye: string = "BYE"; // 'string' primitive type... console.assert(bye.match(re) !== null); // Exactly 3 capital letters... bye = "CIAO"; console.assert(bye.match(re) === null); const a: Array<boolean> = new Array(); a.push(false); // a.push(bye); // Compilation error... but JavaScript execution works!
Rule(s)
- TypeScript fundamentally promotes strong typing in possibly inferring the type of variables from usage contexts, intializations typically.
Example
let size: number; // Explicit typing... let j = 0; // Type inference -> 'number'... const my_array = []; // Implicit 'any' elements in 'my_array' -> bad idea... const my_other_array = new Array<number>(size); // Type inference... my_other_array.push(j);
Example
window.onscroll = function (e) { // 'Event' type is inferred for 'e'... // console.log(e.button); // Compilation error: "Property 'button' does on exist on type 'Event'." } // Versus: window.onscroll = function (e) { console.assert(e.type === 'scroll'); // No compilation error... }
Example (“lookup type”)
class C { a: boolean; b: C['a']; // Lookup type: type of 'b' is that of 'a', i.e., 'boolean'! }
var
versus let
versus const
Rule(s)
- The JavaScript strict mode makes no sense in TypeScript. Note that JavaScript 6 (a.k.a. ES6 or ES2015) module system systematically enables the strict mode.
Example (PURE JavaScript)
"use strict"; var well_declared_variable = "Strict mode is activated from all statement lines in the rest of this source file!"; ill_declared_variable = 2016; // The absence of 'var' raises an error at execution time thanks to the strict mode
Rule(s)
- From JavaScript 6,
var
in JavaScript has poor interest.const
andlet
improve variable declaration.Example (PURE JavaScript)
const my_const = "It cannot change!"; my_const = "One says it cannot change!"; // Execution error if(something) { let this_year = 2017; } // <- 'this_year' dies right there while 'var' (contrary to 'let') makes the variable living throughout its declaration function
Rule(s)
var
in TypeScript has poor interest whileconst
andlet
are compulsory constructs. TypeScript adds the notion of “const” cast withas const
.Example
const F = "Franck"; // 'F' has the *compilation* type "Franck" (inferred) let B = "Barbier"; // 'B' has the type 'string' (inferred) let FB = "FranckBarbier" as const; // 'FB' has the type 'string' (inferred) // FB = "?"; // Compilation error because of 'as const'... -> somehow stupid as this stage? const lasagna = {country: "Italy"}; // 'lasagna' has the type '{country: string}' (inferred) lasagna.country = "France"; // 'const' has precise scope... const foie_gras = {country: "France"} as const; // 'const' cast... // foie_gras.country = "Italy"; // Compilation error! // 'const' cast only applies to references to enum members, string, number, boolean, array or, object literals: // let my_favorite_dish = lasagna as const; // Compilation error!
Rule(s)
- Type cast promotes type conversion when execution conditions allow the attemped conversion. Casts may fail then leading to JavaScript problems when objects are used in the wrong way, e.g., accessing non-extant methods.
- TypeScript offers two equivalent ways of casting variables.
Example (
tsconfig.json
file)"lib": [ // '"target": "ES6"' is enough "DOM", …
Example (casting)
// 'HTMLElement' and 'HTMLCanvasElement' come from the 'DOM' library // Type cast is imposed by the fact that 'createElement' returns a 'HTMLElement' object: let canvas: HTMLCanvasElement = window.document.createElement('canvas') as HTMLCanvasElement; // Alternatively: canvas = <HTMLCanvasElement>window.document.createElement('canvas');
Rule(s)
strictNullChecks
set totrue
(a subset ofstrict
set totrue
) reinforces the checking of dubiousnull
andundefined
.Example (
tsconfig.json
file)"strictNullChecks": true, …
Example (casting)
// By definition, 'getContext' returns 'null' or the canvas' context: const canvas_context = canvas.getContext('2d'); if (canvas_context !== null) // Required by '"strictNullChecks": true' canvas_context.drawImage(image, 0, 0, image.width, image.height);
Example (alternative casting)
// By definition, 'getContext' returns 'null' or the canvas' context: const canvas_context = <CanvasRenderingContext2D>canvas.getContext('2d'); // Required by '"strictNullChecks": true' canvas_context.drawImage(image, 0, 0, image.width, image.height); // Potential failure!
Primitive types Rule(s)
- TypeScript relies on JavaScript primitive types (
boolean
,number
,bigint
,string
,undefined
, andsymbol
).Example
console.log(typeof true); // 'boolean' console.log(typeof 2016); // 'number' console.log(typeof "2016"); // 'string' const Franck: string = "Franck"; console.log(`Hello ${Franck}`); // JavaScript 6 backquotes!
Rule(s)
bigint
has been introduced in JavaScript from 2020."target": "ES2020"
is required.Example
const my_bigint = 1234567890123456789012345678901234567890n; // Note 'n' suffix!
See also
symbol
type
null
andundefined
Rule(s)
- In JavaScript,
null
is a value whileundefined
is both a value and a type.Example Base.ts.zip
console.assert(null == undefined); console.assert(null !== undefined); let undefined_variable; console.log(typeof undefined_variable); // 'undefined' is displayed... console.log(typeof null); // 'object' is displayed...
See also
Enumerated types Rule(s)
- Contrary to JavaScript, TypeScript has a native support for enumerated types.
Example (PURE JavaScript)
const Medal = Object.freeze({ // No new field, no removable field, no writability, etc. Gold: Symbol("Gold"), Silver: Symbol("Silver"), Bronze: Symbol("Bronze") }); console.assert(Object.isFrozen(Medal)); let award = Medal.Gold; console.log(award !== Symbol("Gold")); // 'true' because 'Symbol' objects are unique...
Example
enum Medal { Gold = "Gold", Silver = "Silver", Bronze = "Bronze" } let award: Medal = Medal.Gold; console.log(award); // 'Gold'
Example
enum Gateway_type { BPMN_complexGateway = 'bpmn:complexGateway', BPMN_eventBasedGateway = 'bpmn:eventBasedGateway', BPMN_exclusiveGateway = 'bpmn:exclusiveGateway', BPMN_inclusiveGateway = 'bpmn:inclusiveGateway', BPMN_parallelGateway = 'bpmn:parallelGateway' } … if (!Object.values(Gateway_type).includes(gateway.$type as Gateway_type)) // 'values' => ES2017! throw new Error('Invalid gateway type among ' + JSON.stringify(Gateway_type));
Non-primitive types Rule(s)
- Primitive types have wrapper types (
Boolean
,Number
,BigInt
,String
, andSymbol
) offering “class methods” like, for instance, string to number conversion.- Typically,
number
(base) orNumber
(wrapper) are (as for JavaScript) the way of dealing with essential computations in TypeScript. In this scope, JavaScript bitwise operators allow the direct access of internal representation of numbers.Example
/** JavaScript 'number' primitive type is 64-bit long based on the IEEE 754 standard */ // See also http://speakingjs.com/es5/ch11.html#number_representation console.log(Number.MAX_VALUE); const one: number = Number.parseInt("00000001", 2); // ES2015 if (Number.isNaN(one)) console.log('Number.isNaN(one)'); else console.log('one: ' + one); // '1' const two: Number = new Number(1 << 1); console.log('two(2): ' + two.toString(2)); // '10' const zero: Number = new Number(1 >> 1); console.log('zero: ' + zero); // '0'
Rule(s)
- As in any programming language, possible overflows have to be handled thanks to predefined constant values (see also here…).
Example
/** JavaScript bitwise operators apply on the first significant 32 bits only! */ // How negative numbers are represented: const minus_nine: Number = new Number((~9) | 1); // '00000000000000000000000000001001' -> '11111111111111111111111111110110' -> '11111111111111111111111111110111' console.log('minus_nine: ' + minus_nine); // '-9' // 32 significant bits representing the number are viewed as an *unsigned* 32-bit integer: let x_: Number = new Number(-1 >>> 0); // Unsigned right shift that inserts '0' on the left (BTW: unsigned left shift does not exist!) console.log('-1 >>> 0: ' + x_.toString(2)); // '11111111111111111111111111111111' -> (2^32 - 1) -> 4294967295 console.log(Number.MAX_SAFE_INTEGER.toString(2).length); // '53' since 'Number.MAX_SAFE_INTEGER' is equal to (2^53 - 1) console.assert(Number.MAX_SAFE_INTEGER === -Number.MIN_SAFE_INTEGER); console.log(Number.MAX_VALUE * 2 === Number.POSITIVE_INFINITY); // 'true' console.log(isNaN(Math.sqrt(-1))); // 'true'
See also
Object
typeRule(s)
- JavaScript core architecture relies on the notion of “prototype”.
Example Base.ts.zip
let o = {a: "a"}; console.assert(o.constructor === Object); // 'Object' function... console.assert(Object.getPrototypeOf(o) === Object.prototype); // "Prototype inheritance"...
Rule(s)
- Structured objects with attributes may benefit from being created by means of
Object.create
.Example
let o_ = Object.create(Object.prototype); // <=> 'let o_ = {};' console.log(typeof o_); // 'object'
JavaScript objects may grow and slim!
Example (PURE JavaScript)
let Franck = {}; Object.defineProperty(Franck, "surname", {value: "Barbier", enumerable: true, configurable: true, writable: true}); Object.defineProperty(Franck, "nickname", {value: "Bab", enumerable: true, configurable: false, writable: false}); try { Franck.nickname = "Other nickname"; } catch (error) { // JavaScript, bug: console.log(error.message); // '"nickname" is read-only' } try { delete Franck.nickname; } catch (error) { // JavaScript, bug: console.log(error.message); // 'property "nickname" is non-configurable and can't be deleted' } Franck.skill = "JavaScript"; // Dynamic extension console.log(JSON.stringify(Franck)); // '{"surname":"Barbier","nickname":"Bab","skill":"JavaScript"}' delete Franck.skill; // Dynamic suppression if ("skill" in Franck === false) console.log("'Franck.skill' no longer exists...");
PropertyDescriptor
Example Base.ts.zip
let non_configurable = {boring: "C'est la MAAF que je préfère..."}; Object.seal(non_configurable); /* Bug in JavaScript ('property "boring" is non-configurable and can't be deleted') and compilation error in TypeScript ('The operand of a 'delete' operator must be optional.'): */ // delete non_configurable.boring;
Example Base.ts.zip
const UK = {}; Object.defineProperty(UK, "prime minister", { value: "Boris Johnson", enumerable: true, configurable: false, writable: false }); Object.defineProperty(UK, "secret intelligence service head", { value: "Richard Moore", enumerable: true, configurable: false, writable: false }); Object.defineProperty(UK, "secret intelligence service 007", { value: "James Bond", enumerable: false, configurable: false, writable: false }); console.assert(UK.propertyIsEnumerable("constructor") === false); console.assert(UK.propertyIsEnumerable("secret intelligence service 007") === false); for (const property in UK) { // '"suppressImplicitAnyIndexErrors": true': console.log(`${property}: ${UK[property]}`); // "prime minister" and "secret intelligence service head" only... }
getOwnPropertyNames
Rule(s)
- Primitives of the
Object
type, e.g.,getOwnPropertyNames
, leverage introspection.Example Introspection.js.zip
const Get_attributes = (object) => { return Object.getOwnPropertyNames(object); }; const Get_functions = (object) => { const functions = []; for (const f in object) if (typeof object[f] === "function" /*&& object.hasOwnProperty(f)*/) // Inherited functions (with comment) as well... functions.push(f); return functions; }; const Display_attributes = (object) => { window.alert(Get_attributes(object).join(" - ")); }; const Display_functions = (object) => { window.alert(Get_functions(object).join(" - ")); };
Predefined objects (Document Object Model)
Rule(s)
- JavaScript offers predefined objects with (possible) predefined properties: attributes and/or functions (i.e., “methods”).
window
withalert
,confirm
… base functions in the browser.window.console
,window.document
,window.JSON
,window.Math
,window.navigator
(here…),window.screen
(here…)… are browser-managed objects.window.onload
isundefined
. It may refer to a function object.Example
window.onload = this_is_the_main_program; … // Somewhere else: function this_is_the_main_program() { console.log("All resources are available including downloaded images…"); …
Example (
window.screen
)this._map = window.document.getElementById('map'); // Get the display area (DOM must be ready)... if(this._map) this._map.style.height = window.screen.height + "px";
Example (
window.JSON
)// From 'Object' to 'String': let my_string = window.JSON.stringify(my_object); // From 'String' to 'Object': let my_object = window.JSON.parse(my_string);
Example (
window.Math
)window.alert(window.JSON.stringify(window.location.hostname)); // '"localhost"' may be displayed window.alert(window.Math.E); // '2.718281828459045' is displayed window.alert(window.Math.ceil(Math.E)); // '3' is displayed
Compilation-based types Rule(s)
- TypeScript introduces compilation-based types outside the scope of JavaScript for static type checking:
any
,null
,undefined
,void
,never
, andunknown
.- Difference between
void
andnever
is explained here… Note thatvoid
variables may only be assigned withnull
orundefined
.Example (
null
,undefined
, andvoid
)let x: undefined = undefined; let y: null = null; function f(): void { return undefined; }
Example (
any
)let my_var: any; console.assert(my_var === undefined, "Note that 'undefined' is both the type and its singleton value!"); my_var = {given_name: "Franck"}; console.assert(my_var.given_name === "Franck", "No compilation error...");
Rule(s)
- The difference between
any
andunknown
relies on the fact thatunknown
is similar toany
with less permissivity.Example
let my_other_var: unknown; my_other_var = {given_name: "Franck"}; // console.assert(my_other_var.given_name === "Franck", "Compilation error: 'given_name' does not exist on type 'unknown'"); // if ('given_name' in my_other_var) ... // Compilation error! // Type guard (type predicate) 'o is { given_name: string }': function possess_given_name(o: any): o is { given_name: string } { return 'given_name' in o; } if (possess_given_name(my_other_var)) console.log(my_other_var.given_name); // No compilation error...
Type guard
Rule(s)
- Type checking may be conducted with weak (using
any
typically) or stronger (usingunknown
for instance) type checking.- Type guard (see also here…) is the means for “mapping” TypeScript static checking to JavaScript dynamic checking.
Example (weak) Base.ts.zip
const ws = new WebSocket("ws:127.0.0.1:1963", "FranckBarbier"); ws.onmessage = (event: MessageEvent) => { const d: any = JSON.parse(event.data); // What's inside 'event.data'? const high_quality: boolean = d.high_quality; // No type checking because 'd: any'... };
Example (stronger) Base.ts.zip
const ws = new WebSocket("ws:127.0.0.1:1963", "FranckBarbier"); ws.onmessage = (event: MessageEvent) => { const d: unknown = JSON.parse(event.data); // What's inside 'event.data'? // const high_quality: boolean = d.high_quality; // Compilation error because 'd: unknown'... };
Example (best) Base.ts.zip
interface _Data { high_quality: boolean; } type Data = _Data; // For pedagogy only... function isData(d: any): d is Data { return 'high_quality' in d; } … const ws = new WebSocket("ws:127.0.0.1:1963", "FranckBarbier"); ws.onmessage = (event: MessageEvent) => { const d: any /* or 'unknown' */ = JSON.parse(event.data); if (isData(d)) { // Allow access to 'high_quality' at *COMPILATION TIME*: const high_quality: boolean = d.high_quality; } }
Type guard as predicate
Example (casting remains mandatory)
(model as DMN_Definitions).drgElement .filter(me => is_DMN_Decision(me)) // Type guard is used... .forEach(me => new Deep_learning_DMN_decision(me as DMN_Decision)); // Casting remains mandatory...
Example (type guard as predicate)
(model as DMN_Definitions).drgElement .filter(is_DMN_Decision) // Type guard as predicate... .forEach(me => new Deep_learning_DMN_decision(me)); // No casting!
Optional property
Rule(s)
- The risk of a non-extant property
p
for ano
object is controllable, for example:if (o.p) …
.?
operator may operate such control.Example (risk of failure in access to indexed property)
if (window['splash-screen']) window['splash-screen'].enable('circular');
Example (alternative)
window['splash-screen']?.enable('circular'); // No effect if 'splash-screen' does not exist...
Example
const o: any = {}; o.p?.something; // No bug! // o.p.something; // Bug...
Example
function Hello(print?: (message: string) => void) { print?.("Hello"); } Hello(); // No activation of 'print' function within 'Hello' function... Hello(window.alert); // "Hello" is displayed...
See also
Non-null assertion operator
Rule(s)
- The non-null assertion operator, i.e.,
!
, may help the checking of dubiousnull
andundefined
values (in relation to, again,strictNullChecks
set totrue
or more generallystrict
set totrue
).Example
// I'm sure that 'this._scene.getObjectByName('dlh')' isn't 'null' or 'undefined': console.assert(this._scene.getObjectByName('dlh')!.matrixAutoUpdate === false);
Rule(s)
strictPropertyInitialization
also works in conjunction with!
.Example (
tsconfig.json
file)"strictPropertyInitialization": true, …
class Student { // Initialization must occur in constructor if 'strictPropertyInitialization' is set to 'true' else compilation errors: private _citizen_id: string; private _student_id: string; … }
Example (alternative)
class Student { private _citizen_id!: string; // No initialization required by the compiler while 'strictPropertyInitialization' is set to 'true'... private _student_id: string; … }
Example (overview of
!
and?
operators)interface Patient { medical_record?: string; nick_name: string; } function plastic_surgery_data(p: Patient): string { if (p.nick_name !== "Frankenstein") // Programmer claims that 'p.medical_record' isn't undefined: return p.medical_record!.normalize(); // '!' is required by '"strictNullChecks": true' return "Something else..."; } function plastic_surgery_data_(p: Patient): string | undefined { if (p.nick_name !== "Frankenstein") // Generated JavaScript: 'if (p.medical_record !== undefined) return p.medical_record.normalize(); else return undefined;' return p.medical_record?.normalize(); // '?' is required by '"strictNullChecks": true' return "Something else..."; }
See also
Intersection and union types Rule(s)
Example (union)
let my_var: number | string | null = null;
Example (union) Intersection_union_type.ts.zip
interface Flying_fox { fly: () => void; } interface Fox { run: () => void; } const Observe = function (f: Flying_fox | Fox): void { if ('fly' in f) { f.fly(); } };
Example (intersection) Intersection_union_type.ts.zip
interface Female { highly_skilled: boolean } class Business_woman implements Female { highly_skilled = true; /* Implementation */ } class /* 'interface' also works... */ Manager { /* Empty for test */ } type FemaleAndManager = Female & Manager; const fm: FemaleAndManager = new Business_woman; console.assert(fm.highly_skilled);
Rule(s)
- Intersection and union types are not the solution of multiple inheritance.
Example (multiple inheritance) Intersection_union_type.ts.zip
interface Animal { children: Array<Animal>; } class Mammal implements Animal { children: Array<Animal> = new Array(); /* Implementation */ } class Oviparous implements Animal { children: Array<Animal> = new Array(); /* Implementation */ // eggs: any; // Compilation error! } const o: Mammal & Oviparous = new Mammal; // Property 'eggs' is missing in type 'Mammal' but required in type 'Oviparous'.