Created
May 30, 2024 10:05
-
-
Save sschepis/622218ba18d9e16e068eb17d9d398910 to your computer and use it in GitHub Desktop.
A collection of highly useful Javascript functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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