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:
- Object literal property keys or values that are not unsupported by Json format are ignored.
- 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
Further Readings