Skip to content

Instantly share code, notes, and snippets.

@sschepis
Created May 30, 2024 10:05
Show Gist options
  • Save sschepis/622218ba18d9e16e068eb17d9d398910 to your computer and use it in GitHub Desktop.
Save sschepis/622218ba18d9e16e068eb17d9d398910 to your computer and use it in GitHub Desktop.
A collection of highly useful Javascript functions
// Utility Functions
// Removes leading and trailing whitespace from a string
// Usage: trim(' Hello World ') => 'Hello World'
function trim(str) {
return str.replace(/^\s+|\s+$/g, '');
}
// Merges the properties of one or more source objects into a target object
// Usage: extend({a: 1}, {b: 2}, {c: 3}) => {a: 1, b: 2, c: 3}
function extend(target) {
var sources = [].slice.call(arguments, 1);
sources.forEach(function (source) {
for (var prop in source) {
target[prop] = source[prop];
}
});
return target;
}
// Checks if an object is an array
// Usage: isArray([1, 2, 3]) => true, isArray({}) => false
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
// Creates a new function that, when called, has its `this` keyword set to the provided context
// Usage: const boundFunc = bind(myFunc, myContext); boundFunc();
function bind(fn, context) {
return function() {
return fn.apply(context, arguments);
};
}
// Limits the rate at which a function can be called
// Usage: const debouncedFunc = debounce(myFunc, 250); debouncedFunc();
function debounce(func, wait) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Templating Functions
// Replaces placeholders in a string with corresponding values from an object
// Usage: template('Hello, {name}!', {name: 'John'}) => 'Hello, John!'
function template(str, data) {
return str.replace(/({\w+})/g, function(match, key) {
return data[key.replace(/[{}]/g, '')] || '';
});
}
// DOM Manipulation Functions
// Attaches an event listener to an element
// Usage: addEvent(myElement, 'click', myEventHandler);
function addEvent(elem, type, fn) {
if (elem.addEventListener) {
elem.addEventListener(type, fn, false);
} else if (elem.attachEvent) {
elem.attachEvent('on' + type, function() {
return fn.call(elem, window.event);
});
}
}
// Removes an event listener from an element
// Usage: removeEvent(myElement, 'click', myEventHandler);
function removeEvent(elem, type, fn) {
if (elem.removeEventListener) {
elem.removeEventListener(type, fn, false);
} else if (elem.detachEvent) {
elem.detachEvent('on' + type, fn);
}
}
// Retrieves the computed style of an element
// Usage: getStyle(myElement, 'color') => 'rgb(255, 0, 0)'
function getStyle(elem, name) {
if (elem.style[name]) {
return elem.style[name];
} else if (elem.currentStyle) {
return elem.currentStyle[name];
} else if (document.defaultView && document.defaultView.getComputedStyle) {
name = name.replace(/([A-Z])/g, '-$1').toLowerCase();
return document.defaultView.getComputedStyle(elem, null).getPropertyValue(name);
} else {
return null;
}
}
// Executes a callback function when the DOM is fully loaded
// Usage: ready(myFunc);
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', fn);
} else {
document.attachEvent('onreadystatechange', function() {
if (document.readyState !== 'loading') {
fn();
}
});
}
}
// Array Manipulation Functions
// Filters an array of elements based on a callback function
// Usage: grep([1, 2, 3, 4], x => x % 2 === 0) => [2, 4]
function grep(elems, callback, invert) {
var callbackInverse,
matches = [],
i = 0,
length = elems.length,
callbackExpect = !invert;
for (; i < length; i++) {
callbackInverse = !callback(elems[i], i);
if (callbackInverse !== callbackExpect) {
matches.push(elems[i]);
}
}
return matches;
}
// Function Manipulation Functions
// Ensures that a given function can only be called once
// Usage: const onceFunc = once(myFunc); onceFunc(); onceFunc(); // myFunc is only called once
function once(fn) {
var called = false;
return function() {
if (!called) {
called = true;
return fn.apply(this, arguments);
}
};
}
// Caches the results of expensive function calls and returns the cached result when the same inputs occur again
// Usage: const memoizedFunc = memoize(myExpensiveFunc); memoizedFunc(args); // Cached result is returned for subsequent calls with the same args
function memoize(fn) {
var cache = {};
return function() {
var key = JSON.stringify(arguments);
if (cache[key]) {
return cache[key];
} else {
var result = fn.apply(this, arguments);
cache[key] = result;
return result;
}
};
}
// Object Manipulation Functions
// Creates a deep clone of an object
// Usage: const clonedObj = deepClone(myObj);
function deepClone(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
var clone = Array.isArray(obj) ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
// Asynchronous Functions
// Converts a callback-based function into a Promise-based function
// Usage: const promisifiedFunc = promisify(myCallbackFunc); promisifiedFunc().then(...).catch(...);
function promisify(fn) {
return function() {
var args = Array.prototype.slice.call(arguments);
return new Promise(function(resolve, reject) {
args.push(function(err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
});
fn.apply(null, args);
});
};
}
// Limits the rate at which a function can be called, ensuring that it is not invoked more than once per specified time interval
// Usage: const throttledFunc = throttle(myFunc, 1000); throttledFunc(); // myFunc will be called at most once per second
function throttle(fn, delay) {
var lastCall = 0;
return function() {
var now = Date.now();
if (now - lastCall < delay) {
return;
}
lastCall = now;
return fn.apply(this, arguments);
};
}
// Deeply freezes an object, making it immutable
// Usage: const frozenObj = deepFreeze(myObj); frozenObj.prop = 'new value'; // This will have no effect
function deepFreeze(obj) {
if (typeof obj !== 'object' || obj === null || Object.isFrozen(obj)) {
return obj;
}
Object.freeze(obj);
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
deepFreeze(obj[key]);
}
}
return obj;
}
// Extends the concept of memoization to asynchronous functions
// Usage: const memoizedAsyncFunc = memoizeAsync(myAsyncFunc); memoizedAsyncFunc(args); // Cached result is returned for subsequent calls with the same args
function memoizeAsync(fn) {
var cache = {};
return async function() {
var key = JSON.stringify(arguments);
if (cache[key]) {
return cache[key];
} else {
var result = await fn.apply(this, arguments);
cache[key] = result;
return result;
}
};
}
// Creates a pipeline of functions, where the output of each function becomes the input of the next function
// Usage: const pipelinedFunc = pipe(func1, func2, func3); pipelinedFunc(initialValue);
function pipe(...fns) {
return function(x) {
return fns.reduce((acc, fn) => fn(acc), x);
};
}
// Executes a function immediately on the leading edge of the wait interval, rather than waiting for the interval to complete
// Usage: const leadingDebouncedFunc = debounceLeading(myFunc, 1000); leadingDebouncedFunc(); // myFunc is called immediately, then debounced for 1 second
function debounceLeading(fn, wait) {
var timerId;
return function() {
var context = this;
var args = arguments;
if (!timerId) {
fn.apply(context, args);
}
clearTimeout(timerId);
timerId = setTimeout(() => {
timerId = null;
}, wait);
};
}
// Retries an asynchronous function a specified number of times if it fails
// Usage: const result = await retryAsync(myAsyncFunc, 5, 2000); // Retries myAsyncFunc up to 5 times with a 2-second delay between retries
async function retryAsync(fn, retries = 3, delay = 1000) {
try {
return await fn();
} catch (err) {
if (retries <= 0) {
throw err;
}
await new Promise(resolve => setTimeout(resolve, delay));
return retryAsync(fn, retries - 1, delay);
}
}
// Groups the elements of an array based on a given criteria function
// Usage: const groupedData = groupBy([{age: 25}, {age: 30}, {age: 25}], item => item.age); // {25: [{age: 25}, {age: 25}], 30: [{age: 30}]}
function groupBy(arr, fn) {
return arr.reduce((acc, item) => {
const key = fn(item);
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(item);
return acc;
}, {});
}
// Similar to the built-in `Promise.all()` method, but provides more flexibility
// Usage: const results = await promiseAll([promise1, promise2, promise3]);
function promiseAll(promises) {
return new Promise((resolve, reject) => {
let results = [];
let completedPromises = 0;
promises.forEach((promise, index) => {
promise
.then(result => {
results[index] = result;
completedPromises++;
if (completedPromises === promises.length) {
resolve(results);
}
})
.catch(reject);
});
});
}
// Performs a deep equality comparison between two objects
// Usage: deepEqual({a: 1, b: {c: 2}}, {a: 1, b: {c: 2}}) => true
function deepEqual(obj1, obj2) {
if (obj1 === obj2) {
return true;
}
if (typeof obj1 !== 'object' || typeof obj2 !== 'object' ||
obj1 === null || obj2 === null) {
return false;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
return false;
}
for (const key of keys1) {
if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
// Extends the memoization concept by adding an expiration time to the cached results
// Usage: const memoizedWithExpiryFunc = memoizeWithExpiry(myExpensiveFunc, 60000); // Cached results expire after 1 minute
function memoizeWithExpiry(fn, expiry) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
const { timestamp, result } = cache.get(key);
if (Date.now() - timestamp < expiry) {
return result;
}
cache.delete(key);
}
const result = fn.apply(this, args);
cache.set(key, { timestamp: Date.now(), result });
return result;
};
}
// Combines the behaviors of throttling and debouncing, allowing for both leading and trailing edge invocations
// Usage: const throttledAndDebouncedFunc = throttleWithLeadingAndTrailing(myFunc, 1000); throttledAndDebouncedFunc(); // myFunc is called immediately, then throttled and debounced for 1 second
function throttleWithLeadingAndTrailing(fn, wait) {
let timerId;
let lastArgs;
let lastThis;
let result;
let lastCallTime = 0;
let lastInvokeTime = 0;
function invokeFunc(time) {
const args = lastArgs;
const thisArg = lastThis;
lastArgs = lastThis = undefined;
lastInvokeTime = time;
result = fn.apply(thisArg, args);
return result;
}
function leadingEdge(time) {
lastInvokeTime = time;
timerId = setTimeout(timerExpired, wait);
return invokeFunc(time);
}
function remainingWait(time) {
const timeSinceLastCall = time - lastCallTime;
const timeSinceLastInvoke = time - lastInvokeTime;
const timeWaiting = wait - timeSinceLastInvoke;
return Math.max(0, wait - timeSinceLastCall, timeWaiting);
}
function shouldInvoke(time) {
const timeSinceLastCall = time - lastCallTime;
const timeSinceLastInvoke = time - lastInvokeTime;
return (lastCallTime === 0 || timeSinceLastCall >= wait ||
timeSinceLastCall < 0 || (timeSinceLastInvoke >= wait && timerId));
}
function timerExpired() {
const time = Date.now();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
timerId = setTimeout(timerExpired, remainingWait(time));
}
function trailingEdge(time) {
timerId = undefined;
if (lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
function debounced(...args) {
const time = Date.now();
const isInvoking = shouldInvoke(time);
lastArgs = args;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
if (!timerId) {
return leadingEdge(lastCallTime);
}
timerId = setTimeout(timerExpired, wait);
}
return result;
}
return debounced;
}
export {
trim,
extend,
isArray,
bind,
debounce,
template,
addEvent,
removeEvent,
getStyle,
ready,
grep,
once,
memoize,
deepClone,
promisify,
throttle,
deepFreeze,
memoizeAsync,
pipe,
debounceLeading,
retryAsync,
groupBy,
promiseAll,
deepEqual,
memoizeWithExpiry,
throttleWithLeadingAndTrailing
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment