Skip to content

JS: primer

Essential TypeScript 4: From Beginner to Pro

Logical OR assignment (||=)

  • only assign if the value is value before the assignment is falsy
  • info here
const obj = {
    b: 1,
    c: 0,
    d: false,
    e: null,
    f: undefined,
    g: "",
    h: "hello",
    i: [],
};
obj.a ||= "a"; // only assigns if obj.a is falsy
obj.b ||= 1; //won't assign
obj.c ||= 0; //will assign (0 is falsy)
obj.d ||= "some value"; //will assign
obj.e ||= null; //will assign
obj.f ||= undefined; //will assign
obj.g ||= []; //will assign
obj.h ||= "hello"; //won't assign
obj.i ||= []; //won't assign

Chapter 2: JS primer 1

  • null means lack of memory reference, undefined means memory reference allocated, thus variable initialized, but not assigned a value
  • primitive types: number, string, boolean, null, undefined, symbol. and object as non-primitive types.
  • in js, values has types, not the variables themselves., changing the value (eg. from string to number) will change the variable type => JS is dynamically typed language
  • typeof null is object
  • type coercion: js runtime automatically converts one type to another.
  • anything added/subtracted..etc. with NaN is NaN eg. NaN + 1 = NaN, 1 + undefined = NaN, 1 + '1' + undefined = NaN
  • anything added/subtracted..etc. with string is converted to string eg. "1" + 1 = "11", "1" - 1 + undefined = 11undefined
  • ?? is the nullish coalescing operator
  • x?.y is the optional chaining operator: only stops if x is not null or undefined, NaN?.y will throw, 1?.y will throw

Chapter 3: JS primer 2

constructor functions

  • for c constructor function to inherit from another constructor function, you need 2 things:
    1. calling the super from the inherited function by calling Constructor2.call(this, ...args), and the result is that the new object has this context referring to constructor function 2.
    2. secondly, the constructor 2 needs to inherit the prototype of constructor function 1, then extends it if needed.
function Constructor1(name, age) {
    this.name = name;
    this.age = age;
}

Constructor1.prototype.sayHello = function () {
    console.log(`Hello, my name is ${this.name}`);
};

function Constructor2(name, age, job) {
    Constructor1.call(this, name, age); // 1. call super constructor, passing `this` as the context of created object
    this.job = job;
}

Object.setPrototypeOf(Constructor2.prototype, Constructor1.prototype); // 2. inherit prototype from super constructor

// 3. extend prototype with new methods
Constructor2.prototype.introduce = function () {
    console.log(`Hello, my name is ${this.name} and I am a ${this.job}`);
};

const obj = new Constructor2("Adam", 30, "developer");
obj.sayHello(); // Hello, my name is Adam
obj.introduce(); // Hello, my name is Adam and I am a developer
  • define static methods on constructor function
function Constructor(name, age) {
    this.name = name;
    this.age = age;
    Constructor.count = Constructor.count ? Constructor.count + 1 : 1;
}

Constructor.prototype.instanceMethod = function () {
    console.log(`Hello, my name is ${this.name}`); // instance method
};

Constructor.staticMethod = function () {
    console.log("static method"); // static method
};

Constructor.staticVariable = "static variable"; // static variable
Constructor.count = 0;

const obj = new Constructor("Adam", 30);
const obj2 = new Constructor("Bob", 40);

console.log(Constructor.count); // 2

Iterators

  • Iterators are objects that return a sequence of values.
  • An iterator defines a function named next that returns an object with value and done properties, where value is the next value in the sequence and done is a boolean that indicates if there are more values to be returned.
class Iterator<T> {
    arr = [] as T[];
    index = 0;
    constructor(arr: T[]) {
        this.arr = arr;
        this.index = 0;
    }
    next() {
        if (this.index < this.arr.length) {
            return {
                value: this.arr[this.index++],
                done: false,
            };
        } else {
            return {
                done: true,
            };
        }
    }
}

const iterator = new Iterator([1, 2, 3, 4, 5]);
for (const { value, done } of iterator.next()) {
    console.log({ value, done });
    if (done) break; // break out of loop if done
}

Generators

  • Generators are:
    • functions that can be paused and resumed at any time.
    • has yield keyword to produce the values in the sequence
  • generator can be helpful to iterate through all values of an iterator.
const iterator = new Iterator([1, 2, 3, 4, 5]);
const Generator = function* (iterator: Iterator<number>) {
    let current = iterator.next();
    while (!current.done) {
        yield current.value;
        current = iterator.next();
    }
};

const myGenerator = new Generator(iterator); // create a generator object, that returns a sequence (array) of values // [1, 2, 3, 4, 5]
for (const value of myGenerator) {
    console.log(value);
}
  • each iterator can have a generator function to loop through its content using a special property on the iterator class named *[Symbol.iterator] that defines a generator function as in the example below.
class Iterator<T> {
    arr = [] as T[];
    index = 0;
    constructor(arr: T[]) {
        this.arr = arr;
        this.index = 0;
    }
    next() {
        if (this.index < this.arr.length) {
            return {
                value: this.arr[this.index++],
                done: false,
            };
        } else {
            return {
                done: true,
            };
        }
    }

    *[Symbol.iterator]() {
        let current = this.next();
        while (!current.done) {
            yield current.value;
            current = this.next();
        }
    }
}

const iterator2 = new Iterator([1, 2, 3, 4, 5]);
for (const value of iterator2) {
    console.log(value);
}
  • and for more robust, generator can be shared between multiple iterators.
interface IIterator<T> {
    next(): { value: T; done: boolean } | { value: undefined; done: boolean };
    arr: any[];
    index: number;
}

const Generator = function* <T>(iterator: IIterator<T>) {
    let current = iterator.next();
    while (!current.done) {
        yield current.value;
        current = iterator.next();
    }
};

class Iterator<T> implements IIterator<T> {
    arr = [] as T[];
    index = 0;
    constructor(arr: T[]) {
        this.arr = arr;
        this.index = 0;
    }
    next() {
        if (this.index < this.arr.length) {
            return {
                value: this.arr[this.index++],
                done: false,
            };
        } else {
            return {
                value: undefined,
                done: true,
            };
        }
    }

    *[Symbol.iterator]() {
        yield* Generator(this);
    }
}

const iterator2 = new Iterator([1, 2, 3, 4, 5]);
for (const value of iterator2) {
    console.log(value);
}
  • The Symbol.iterator property is used to denote the default iterator for an object.

Most important thing about iterators is that you can use them in for of loops invoking its generator (if exists).

Collections

  • Collections are objects that can be iterated over.
  • collections in js cover arrays, sets, maps, strings, buffers, typed arrays, and arguments objects.
  • The most important collection is array(indexed by number) and object(indexed by keys).
  • maps vs objects:
    • objects only use strings as keys, maps use any type of keys.

Chapter 14: working with JS

  • compiler options for working with JS:
    • allowJs: includes all JS that are in the src folder into the dist one.
    • checkJs: checks the JS for errors.
  • comments that can direct the TS compiler:
    • // @ts-check: at the beginning of the file, this directive tells the compiler to check the file for errors (if and only if checkJs is set to true).
    • // @ts-nocheck: at the beginning of the file, this directive tells the compiler to ignore this file and do not check it.

Describing types is JS files

Using Comments to Describe Types (JsDoc)

Using declaration files

  • Declaration files, also referred to as type definition files, provide a way to describe JavaScript code to the TypeScript file without having to change the source code file.
  • declaration files has .d.ts extension and the name of the file corresponds to the JS file which is describing and in the same folder.
  • type declarations start with declare keyword.
  • Type declaration files take precedence over JSDoc comments when both are used to describe JavaScript code.
  • Any feature that is not described in the type declaration file will not be visible to the TypeScript compiler.
  • compiler will stop analyzing JS file, if a .d.ts file is provided, and the .d.ts file will be the single source of truth for types.

Describing Third-Party JavaScript Code

  • paths compiler options tells the compiler where to look for type declarations files (.d.ts)
  • traceResolution compiler option: compiler will report the progress as it attempts to locate each module.
  • package.json types property tells the compiler where to find declaration files for this package as:
{
    "types": "types/index.d.ts"
}

Generating Declaration Files

  • If your code is going to be used by other projects, you can ask the compiler to generate declaration files alongside the pure JavaScript
  • The compiler won’t generate declaration files when the allowJS option is enabled