Skip to main content

Useful Snippets and Pitfalls

Type checking

Snippet
function isBoolean(value) {
return value === true || value === false;
}

function isNumber(value) {
return typeof(value) === 'number';
}

function isNumber(value) {
return typeof(value) === 'number';
}

function isNull(value) {
return value === null;
}

function isString(value) {
return typeof value === 'string';
}

function isSymbol(value) {
return typeof value === 'symbol';
}

function isUndefined(value) {
return value === undefined;
}

function isArray(value) {
return Array.isArray(value);
}

// Alternative to isArray if using built-in function is not allowd
export function isArrayAlt(value) {
if (value === null || value === undefined) return false;

return value.constructor === Array;
}

function isFunction(value) {
return typeof value === 'function';
}

// Return true if value is an object (e.g. arrays, functions, objects, etc,
// but not including null and undefined), false otherwise.
function isObject(value) {
if (value === null || value === undefined) return false;

const type = typeof value;
return type === 'object' || type === 'function';
}

// A plain object, or a Plain Old JavaScript Object (POJO), is any object whose prototype
// is Object.prototype or an object created via Object.create(null).
function isPlainObject(value) {
if (value === null || value === undefined) return false;

const prototype = Object.getPrototypeOf(value);
return prototype === null || prototype === Object.prototype;
}

// Alternative to isPlainObject, Lodash's implementation.
function isPlainObjectAlternative(value) {
if (!isObject(value)) return false;

// For objects created via Object.create(null);
if (Object.getPrototypeOf(value) === null) return true;

let proto = value;

while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}

return Object.getPrototypeOf(value) === proto;
}

Deep Cloning

Easiest way is to simply stringify it with JSON.stringify and parse it back with JSON.parse, but this method comes with limitations:

  1. Object literal property keys or values that are not unsupported by Json format are ignored.
  2. Values might be falsely parased after stringified and requires special handling (for instance, BigInt)
// Json suppported data types works fine
let a = {a: '1', b: {c: 2}, d: null}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {a: '1', b: {c: 2}, d: null}

// ...

// Flaws when Json-unsupported data types involved
let a = {a: '1', b: {c: 2}, 'NaN': NaN}
a[Symbol()] = 'symbol key ignored'
json_string = JSON.stringify(a)
let b = JSON.parse(json_string)

// Note symbol-keyed property missing after stringify
// and NaN value becomes null after copied
console.log(a) // {a: '1', b: {c: 2}, NaN: NaN, Symbol(): 'symbol key ignored'}
console.log(json_string) // {"a":"1","b":{"c":2},"NaN":null}
console.log(b) // {a: '1', b: {…}, NaN: null}
console.log(a['NaN'] === b['NaN']) // false, cause NaN !== null

Another way is through recusively copy down nested properties:

function deepClone(value) {
if (typeof value !== 'object' || value === null) return value;

if (Array.isArray(value)) {
return value.map((item) => deepClone(item));
}

return Object.fromEntries(
Object.entries(value).map(([key, value]) => [key, deepClone(value)]),
);
}
for..in v.s Object.keys

for..in captures both own and inherited keys while Object.keys only captures object's own properties. Object.keys is usally what we want.

for..in v.s .forEach

var par = { prop1 : "some val" };
var obj = Object.create(par);
obj.prop2 = "some other val";

console.log("^ for...in: ");
for(key in obj){
console.log(`${key}: ${obj[key]}`);
}

// ^ for...in:
// prop2: some other val
// prop1: some val

console.log("^ forEach: ");
Object.keys(obj).forEach((key)=>{
console.log(`${key}: ${obj[key]}`);
})

// ^ forEach:
// prop2: some other val