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.keysfor..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
Currying
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
}
// Note that the undefined check is important here, if
// curried function is called without arguments
return (arg) => arg === undefined
? curried.apply(this, args)
: curried.apply(this, [...args, arg]);
};
}
Debounce vs Throttle
function debounce(func, wait) {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(()=>{
const context = this
func.apply(context, args)
}, wait)
}
}
function throttle(func, wait = 0) {
let shouldThrottle = false;
return function (...args) {
if (shouldThrottle) return;
shouldThrottle = true;
setTimeout(function () {
shouldThrottle = false;
}, wait);
func.apply(this, args);
};
}
Further Readings
Async Lock with Promise
// https://www.linkedin.com/pulse/asynchronous-locking-using-promises-javascript-abdul-ahad-o7smf/
class Lock {
constructor() {
this.isLocked = false;
this.queue = [];
}
async acquire() {
return new Promise((resolve) => {
if (!this.isLocked) {
this.isLocked = true;
resolve();
} else {
this.queue.push(resolve);
}
});
}
release() {
if (this.queue.length > 0) {
const resolve = this.queue.shift();
resolve();
} else {
this.isLocked = false;
}
}
}
// Example usage:
const myLock = new Lock();
async function asyncFunction() {
await myLock.acquire();
try {
// Code that requires exclusive access to a shared resource
console.log('Accessing shared resource...');
await someAsyncOperation();
} finally {
myLock.release();
}
}
asyncFunction();
---
<MyGiscus/>