typescript

January 14, 2021

dynamic imports:

Nullish Coalescing Operator

Checks the left-hand side of the operator for undefined or null. It will return the left hand side if it is falsy but not undefined or null.

null ?? "Hello World"; // “Hello World”
false ?? "Hello World"; // false
0 ?? "Hello World"; // 0
undefined ?? "Hello World"; // “Hello World”
"" ?? "Hello World"; // ''

Logical OR Operator

Checks the left-hand side of the operator for a falsy value. It will return the right hand side if falsy.

null || "Hello World"; // “Hello World”
false || "Hello World"; // “Hello World”
0 || "Hello World"; // “Hello World”
undefined || "Hello World"; // “Hello World”
10 || "Hello World"; // 10
true || "Hello World"; // true
"" || "Hello World"; // 'Hello World'

async

async IIFE

(async () => {
  // code goes here
})();

global definition

declare namespace NodeJS {
  interface Global {
    APPD_ENABLED: string;
  }
}

Boolean as type guard filter

let list = [1, 2, 3, 4, null, 7, null, 9, "yo"];
let filteredList = list.filter(Boolean).filter((v) => typeof v === "number");
console.log(filteredList);

Does not work, type error.

let list = [1, 2, 3, 4, null, 7, null, 9, "yo"];
let filteredList: NonNullable<number>[];
filteredList = list.filter(Boolean);
console.log(filteredList);

Works with strict null checks.

let list = [1, 2, 3, 4, null, 7, null, 9, "yo"];
let filteredList: NonNullable<number>[];
// filteredList = list.filter<NonNullable<number>>(Boolean as any); // would remove nulls but allow strings
filteredList = list
  .filter<NonNullable<number>>(Boolean as any)
  .filter((v) => typeof v === "number");
console.log(filteredList);

BigInt

describe.each([
  [30, 1073741824, "000001000000000000000000000000000000"],
  [31, 2147483648, "000010000000000000000000000000000000"],
  [32, 4294967296, "000100000000000000000000000000000000"],
  [33, 8589934592, "001000000000000000000000000000000000"],
  [34, 17179869184, "010000000000000000000000000000000000"],
  [35, 34359738368, "100000000000000000000000000000000000"],
  // [36, 68719476736, '1000000000000000000000000000000000000'],
])(
  `JS large number math`,
  (pos: number, expectedResult: number, expectedBinaryString: string) => {
    test(`should return ${expectedResult}`, () => {
      // expect(1 << pos).toEqual(expectedResult); // after pos 30 next bit is negative and starts at 1
      expect(Math.pow(2, pos)).toEqual(expectedResult);
    });

    test(`convertToBinaryString(${expectedResult})`, () => {
      expect(expectedBinaryString.length).toBe(36);
      expect(convertToBinaryString(BigInt(expectedResult))).toBe(
        expectedBinaryString,
      );
    });

    test(`parseInt`, () => {
      expect(parseInt(expectedBinaryString, 2)).toBe(expectedResult);
    });
  },
);

Binary & Bitwise

Create a zero padded binary number.

const convertToBinaryString = (num: bigint) =>
  num.toString(2).padStart(36, "0");
const num = 51331021;
console.log(convertToBinaryString(BigInt(num))); // 000000000011000011110011111111001101

Convert a binary string to number.

console.log(parseInt("000000000011000011110011111111001101", 2)); // 51331021

Set a bit for 32 bit signed int.

1 << 4; // 16 or 000000000000000000000000000000010000

Get a bit at a location

const getBit = (n: number, bitIndex: number) => {
  const bitMask = 1 << bitIndex;
  const result = n & bitMask;
  return result >>> bitIndex;
};

console.log(getBit(n, 3)); // 0
console.log(getBit(n, 4)); // 1
console.log(getBit(n, 5)); // 1
console.log(getBit(n, 6)); // 0

Turn on a bit

const setBit = (n: number, bitIndex: number) => {
  const bitMask = 1 << bitIndex;
  return n | bitMask;
};

console.log((0b10110001).toString(2)); // 10110001
console.log(setBit(0b10110001, 2).toString(2)); // 10110101

Clear a bit

const clearBit = (n: number, bitIndex: number) => {
  const bitMask = ~(1 << bitIndex);
  return n & bitMask;
};

console.log(n.toString(2)); // 10110101
console.log(clearBit(n, 2).toString(2)); // 10110001

Bitwise find in array

const foundIndex = (index: number) => {
  return Boolean(~index);
};

console.log(foundIndex(arr.indexOf(9))); // false
console.log(foundIndex(arr.indexOf(100))); // false
console.log(foundIndex(arr.indexOf(3))); // true

Flags

let val = 0b0;

enum Flags {
  NONE = 0,
  SELF = 1,
  NEIGHBOR1 = 2,
  NEIGHBOR2 = 4,
  NEIGHBOR3 = 8,
  NEIGHBOR4 = 16,
  NEIGHBOR5 = 32,
  NEIGHBOR6 = 64,
  NEIGHBOR7 = 128,
  NEIGHBOR8 = 256,
  OTHERDATA = 512,
}

const currentFlags = {
  self: 1,
  neighbor1: 0,
  neighbor2: 1,
  neighbor3: 1,
  neighbor4: 0,
  neighbor5: 1,
  neighbor6: 0,
  neighbor7: 0,
  neighbor8: 1,
  otherdata: 1,
};

const getNeighborsMask = 0b0111111110;

const isEmpty = (v: number) => !(v > Flags.NONE);

console.log(`Initial value:  ${val} : `, val.toString(2), isEmpty(val));
console.log("");

console.log(`***** SETTING *****`);
// Setting
Object.entries(currentFlags).forEach(([_, v], i) => {
  val |= v << i;
});
console.log(`  Final value: ${val} : `, val.toString(2), isEmpty(val));

console.log("");
console.log(`***** READING *****`);
// Reading
console.log(`self is : ${val & Flags.SELF}`);
console.log(`neighbor1 is : ${val & Flags.NEIGHBOR1}`);
console.log(`neighbor2 is : ${val & Flags.NEIGHBOR2}`);
console.log(`neighbor3 is : ${val & Flags.NEIGHBOR3}`);
console.log(`neighbor4 is : ${val & Flags.NEIGHBOR4}`);
console.log(`neighbor5 is : ${val & Flags.NEIGHBOR5}`);
console.log(`otherData is : ${val & Flags.OTHERDATA}`);

console.log("");
console.log(`     -----     `);
Object.keys(currentFlags).forEach((k, i) => {
  const key: string = k.toUpperCase();
  const flag: number = Flags[key];
  if (val & flag) {
    // console.log(`${i} bit is set.`)
    console.log(`${k} is set.`);
  } else {
    console.log(`${k} is not set.`);
  }
});

console.log("");
console.log(`     -----     `);
const cn = val & getNeighborsMask;
// console.log(`isAnyNeighborSet: `, val > Flags.SELF); // only if no other data type is after neighbors
console.log(`isAnyNeighborSet mask: `, !isEmpty(cn));

console.log("");
console.log(`***** UNSET *****`);

// unset neighbors only
Object.keys(currentFlags).forEach((k, i) => {
  const key: string = k.toUpperCase();
  const flag: number = Flags[key];
  if (flag > Flags.SELF && flag < Flags.OTHERDATA) {
    val &= ~(1 << i);
  }
});
console.log(`self is : ${val & Flags.SELF}`);
console.log(`neighbor1 is : ${val & Flags.NEIGHBOR1}`);
console.log(`neighbor2 is : ${val & Flags.NEIGHBOR2}`);
console.log(`neighbor3 is : ${val & Flags.NEIGHBOR3}`);
console.log(`neighbor4 is : ${val & Flags.NEIGHBOR4}`);
console.log(`neighbor5 is : ${val & Flags.NEIGHBOR5}`);
console.log(`otherData is : ${val & Flags.OTHERDATA}`);

const cn2 = val & getNeighborsMask;
// console.log(`isAnyNeighborSet: `, val > Flags.SELF); // only if no other data type is after neighbors
console.log(`isAnyNeighborSet mask: `, !isEmpty(cn2));
Output
"use strict";
let val = 0b0;
var Flags;
(function (Flags) {
  Flags[(Flags["NONE"] = 0)] = "NONE";
  Flags[(Flags["SELF"] = 1)] = "SELF";
  Flags[(Flags["NEIGHBOR1"] = 2)] = "NEIGHBOR1";
  Flags[(Flags["NEIGHBOR2"] = 4)] = "NEIGHBOR2";
  Flags[(Flags["NEIGHBOR3"] = 8)] = "NEIGHBOR3";
  Flags[(Flags["NEIGHBOR4"] = 16)] = "NEIGHBOR4";
  Flags[(Flags["NEIGHBOR5"] = 32)] = "NEIGHBOR5";
  Flags[(Flags["NEIGHBOR6"] = 64)] = "NEIGHBOR6";
  Flags[(Flags["NEIGHBOR7"] = 128)] = "NEIGHBOR7";
  Flags[(Flags["NEIGHBOR8"] = 256)] = "NEIGHBOR8";
  Flags[(Flags["OTHERDATA"] = 512)] = "OTHERDATA";
})(Flags || (Flags = {}));
const currentFlags = {
  self: 1,
  neighbor1: 0,
  neighbor2: 1,
  neighbor3: 1,
  neighbor4: 0,
  neighbor5: 1,
  neighbor6: 0,
  neighbor7: 0,
  neighbor8: 1,
  otherdata: 1,
};
const getNeighborsMask = 0b0111111110;
const isEmpty = (v) => !(v > Flags.NONE);
console.log(`Initial value:  ${val} : `, val.toString(2), isEmpty(val));
console.log("");
console.log(`***** SETTING *****`);
// Setting
Object.entries(currentFlags).forEach(([_, v], i) => {
  val |= v << i;
});
console.log(`  Final value: ${val} : `, val.toString(2), isEmpty(val));
console.log("");
console.log(`***** READING *****`);
// Reading
console.log(`self is : ${val & Flags.SELF}`);
console.log(`neighbor1 is : ${val & Flags.NEIGHBOR1}`);
console.log(`neighbor2 is : ${val & Flags.NEIGHBOR2}`);
console.log(`neighbor3 is : ${val & Flags.NEIGHBOR3}`);
console.log(`neighbor4 is : ${val & Flags.NEIGHBOR4}`);
console.log(`neighbor5 is : ${val & Flags.NEIGHBOR5}`);
console.log(`otherData is : ${val & Flags.OTHERDATA}`);
console.log("");
console.log(`     -----     `);
Object.keys(currentFlags).forEach((k, i) => {
  const key = k.toUpperCase();
  const flag = Flags[key];
  if (val & flag) {
    // console.log(`${i} bit is set.`)
    console.log(`${k} is set.`);
  } else {
    console.log(`${k} is not set.`);
  }
});
console.log("");
console.log(`     -----     `);
const cn = val & getNeighborsMask;
// console.log(`isAnyNeighborSet: `, val > Flags.SELF); // only if no other data type is after neighbors
console.log(`isAnyNeighborSet mask: `, !isEmpty(cn));
console.log("");
console.log(`***** UNSET *****`);
// unset neighbors only
Object.keys(currentFlags).forEach((k, i) => {
  const key = k.toUpperCase();
  const flag = Flags[key];
  if (flag > Flags.SELF && flag < Flags.OTHERDATA) {
    val &= ~(1 << i);
  }
});
console.log(`self is : ${val & Flags.SELF}`);
console.log(`neighbor1 is : ${val & Flags.NEIGHBOR1}`);
console.log(`neighbor2 is : ${val & Flags.NEIGHBOR2}`);
console.log(`neighbor3 is : ${val & Flags.NEIGHBOR3}`);
console.log(`neighbor4 is : ${val & Flags.NEIGHBOR4}`);
console.log(`neighbor5 is : ${val & Flags.NEIGHBOR5}`);
console.log(`otherData is : ${val & Flags.OTHERDATA}`);
const cn2 = val & getNeighborsMask;
// console.log(`isAnyNeighborSet: `, val > Flags.SELF); // only if no other data type is after neighbors
console.log(`isAnyNeighborSet mask: `, !isEmpty(cn2));
Compiler Options
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "moduleResolution": 2,
    "target": "ES2017",
    "jsx": "React",
    "module": "ESNext"
  }
}

Playground Link: Provided

Easier to Read Binary Enums

enum Flags {
  NONE = 0,
  SELF = 1 << 0,
  NEIGHBOR1 = 1 << 1,
  NEIGHBOR2 = 1 << 2,
  NEIGHBOR3 = 1 << 3,
  NEIGHBOR4 = 1 << 4,
}

// or, they are equivalent

enum Flags2 {
  NONE = 0,
  SELF = 1,
  NEIGHBOR1 = 2 ** 1,
  NEIGHBOR2 = 2 ** 2,
  NEIGHBOR3 = 2 ** 3,
  NEIGHBOR4 = 2 ** 4,
}

// original
enum Flags {
  NONE = 0,
  SELF = 1,
  NEIGHBOR1 = 2,
  NEIGHBOR2 = 4,
  NEIGHBOR3 = 8,
  NEIGHBOR4 = 16,
}

Data Structures

Map can be very fast.

Set

  • No duplicates
  • Converting from array to set and vice versa is easy
  • Large datasets can have a performance impact, usually very fast
  • Values follow object equality rules
type point = [number, number, number];
const testSet = new Set<point>();
const newPoint = [4, 3, 2] as point;
const points: point[] = [[0, 1, 1], [1, 1, 2], [3, 1, 1], [0, 2, 1], newPoint];
points.forEach((p) => {
  testSet.add(p);
});

console.log(testSet.size); // 5
console.log(testSet.has([1, 1, 2])); // object equality, false
console.log(testSet.has(newPoint)); // true

workaround for complex lookups

type point = [number, number, number];
const testSet = new Set<string>();
const newPoint = [4, 3, 2] as point;
const points: point[] = [[0, 1, 1], [1, 1, 2], [3, 1, 1], [0, 2, 1], newPoint];
points.forEach((p) => {
  testSet.add(p.toString());
});

console.log(testSet.size); // 5
console.log(testSet.has([1, 1, 2].toString())); // true
console.log(testSet.has(newPoint.toString())); // true

Performance

--perf-basic-profs flame graph