typescript
January 14, 2021dynamic imports:
- https://mariusschulz.com/blog/typescript-2-4-dynamic-import-expressions
- http://2ality.com/2017/01/import-operator.html
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
- https://fettblog.eu/boolean-in-javascript-and-typescript/
- https://mariusschulz.com/blog/conditional-types-in-typescript
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
- https://medium.com/@LindaVivah/learn-how-to-read-binary-in-5-minutes-dac1feb991e
- https://lucasfcosta.com/2018/12/25/bitwise-operations.html
- https://blog.logrocket.com/interesting-use-cases-for-javascript-bitwise-operators/
- https://codeforwin.org/2018/05/10-cool-bitwise-operator-hacks-and-tricks.html
- https://www.alanzucconi.com/2015/07/26/enum-flags-and-bitwise-operators/
- https://graphics.stanford.edu/~seander/bithacks.html
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