Skip to main content

Сниппеты JavaScript. Часть 1

Число

Решение проблемы чисел с плавающей точкой

console.log(0.1 + 0.2 === 0.3) // false

const areEqual = (x, y) => x - y < Number.EPSILON

const result = 0.1 + 0.2

if (areEqual(result, 0.3)) {
console.log('0.1 + 0.2 = 0.3') // 0.1 + 0.2 = 0.3
}

Четность/нечетность числа

const oddOrEven = (n) => (n % 2 === 0 ? 'even' : 'odd')
// or
const oddOrEven = (n) => (n & 1 ? 'odd' : 'even')

Находится ли число в указанном диапазоне?

const isInRange = (n, start, end = null) => {
if (end && start > end) [end, start] = [start, end]
return end === null ? n >= 0 && n < start : n >= start && n < end
}

isInRange(2, 0, 10) // true
isInRange(2, 3, 9) // false

Округление числа до указанного количества знаков после запятой

const round = (n, d = 0) => +`${Math.round(`${n}e${d}`)}e-${d}`

Арифметическая прогрессия

const arithmeticProgress = (n, max) =>
Array.from({ length: Math.ceil(max / n) }, (_, i) => (i + 1) * n)

Геометрическая прогрессия

const geometricProgress = (end, start = 1, step = 2) =>
Array.from({ length: ~~(Math.log(end / start) / Math.log(step)) + 1 }).map(
(_, i) => start * step ** i
)

Наибольший общий делитель

const gcd = (...nums) => {
const _gcd = (x, y) => (!y ? x : gcd(y, x % y))
return nums.reduce((a, b) => _gcd(a, b))
}

Наименьшее общее кратное

const lcm = (...nums) => {
const gcd = (x, y) => (!y ? x : gcd(y, x % y))
const _lcm = (x, y) => (x * y) / gcd(x, y)
return nums.reduce((a, b) => _lcm(a, b))
}

Преобразование арабских чисел в римские

const convertToRoman = (n) => {
const lookup = [
['M', 1000],
['CM', 900],
['D', 500],
['CD', 400],
['C', 100],
['XC', 90],
['L', 50],
['XL', 40],
['X', 10],
['IX', 9],
['V', 5],
['IV', 4],
['I', 1]
]
return lookup.reduce((a, [k, v]) => {
a += k.repeat(~~(n / v))
n = n % v
return a
}, '')
}

Среднее значение

const average = (...nums) => nums.reduce((a, c) => a + c, 0) / nums.length

Случайное целое число в заданном диапазоне

const getRandomInt = (min, max) => ~~(min + Math.random() * (max - min + 1))

Случайное положительное число в заданном диапазоне

const getRandomPosiviteNum = (min, max, p) => {
if (min < 0 || min >= max) return 'invalid'
const n = min + Math.random() * (max - min + 1)
return !p ? ~~n : n.toFixed(p)
}

Случайный элемент массива

const getRandomItem = (arr) => arr[~~(Math.random() * arr.length)]
// or
const getRandomItem = (arr) => arr[getRandomInt(0, arr.length - 1)]

Площадь треугольника

const getTriangleSquare = (a, b, c) => {
const p = (a + b + c) / 2
return (p * (p - a) * (p - b) * (p - c)) ** 0.5
}

Преобразование радиан в градусы и обратно

const radToDeg = (rad) => (rad * 180) / Math.PI

const degToRad = (deg) => (deg * Math.PI) / 180

Преобразование километров в мили и обратно

const kmToM = (km) => km * 0.621371

const mToKm = (m) => m / 0.621371

Преобразование градусов Цельсия в градусы Фаренгейта и обратно

const cToF = (c) => c * 1.8 + 32

const fToC = (f) => (f - 32) / 1.8

Форматирование миллисекунд в дни, часы и т.д.

const format = (ms) => {
if (ms < 0) ms = -ms
const time = {
day: ~~(ms / 86400000),
hour: ~~(ms / 3600000) % 24,
minute: ~~(ms / 60000) % 60,
second: ~~(ms / 1000) % 60,
millisecond: ~~ms % 1000
}
return Object.entries(time)
.filter((val) => val[1] !== 0)
.map(([k, v]) => `${v} ${k}${v !== 1 ? 's' : ''}`)
.join(', ')
}

format(34325055574)
// 397 days, 6 hours, 44 minutes, 15 seconds, 574 milliseconds

Форматирование байтов

const formatBytes = (num, precision = 3, addSpace = true) => {
const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const space = addSpace ? ' ' : ''
const sign = num < 0 ? -num : num

if (Math.abs(num) < 1) return num + space + UNITS[0]

const exponent = Math.min(~~(Math.log10(sign) / 3), UNITS.length - 1)
const _num = +(sign / 1000 ** exponent).toPrecision(precision)

return (num < 0 && '-') + _num + space + UNITS[exponent]
}

formatBytes(-27145424323.5821, 5) // '-27.145 GB'
formatBytes(123456789, 3, false) // '123MB'

Маскировка чисел

const mask = (num, count = 4, mask = '*') =>
`${num}`.slice(-count).padStart(`${num}`.length, mask)

mask(1234567890, 3) // '*******890'
mask(1234567890, -4, '$') // '$$$$567890'

Расстояние между двумя точками

const distance = (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1)

distance(0, 0, 10, 10) // 14.142135623730951

Строка

Инверсия строки

const reverseWord = (str) => [...str].reverse().join('')
reverseWord('foo') // oof

const reverseWords = (str) => str.split(' ').reverse().join(' ')
reverseWords('hello world') // world hello
// or
const reverseWords = (str) =>
str
.split(' ')
.map((w) => [...w].reverse().join(''))
.join(' ')
reverseWords('hello world') // olleh dlrow

"Капитализация" строки

const capitilizeWord = (str) => `${str[0].toUpperCase()}${str.slice(1)}`
capitilizeWord('foo') // Foo

const capitilizeWords = (str) =>
str
.split(' ')
.map((w) => `${w[0].toUpperCase()}${w.slice(1)}`)
.join(' ')

capitilizeWords('foo bar') // Foo Bar

Сокращение строки

const truncateWord = (str, n) =>
str.length > n ? str.slice(0, n > 3 ? n : 3) + '...' : str

truncateWord('JavaScript', 4) // Java...

const truncateWords = (str, n) => str.split(' ').slice(0, n).join(' ')
truncateWords('JavaScript is awesome!', 1) // JavaScript

Количество вхождений подстроки

const subCount = (str, sub) => {
let c = 0,
i = 0
while (true) {
const r = str.indexOf(sub, i)
if (r !== -1) [c, i] = [c + 1, r + 1]
else return c
}
}

subCount('tiktok tik tok tik', 'tik') // 3

Преобразование строки

const toTitleCase = (str) =>
str
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
.map((i) => `${i[0].toUpperCase()}${i.slice(1)}`)
.join(' ')

toTitleCase('hello world') // Hello World
toTitleCase('hello-world') // Hello World

const toCamelCase = (str) => {
let s =
str &&
str
.match(
/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g
)
.map((c) => `${c[0].toUpperCase()}${c.slice(1).toLowerCase()}`)
.join('')

return `${s[0].toLowerCase()}${s.slice(1)}`
}

toCamelCase('hello world') // helloWorld
toCamelCase('hello-world') // helloWorld

const toSnakeCase = (str) =>
str &&
str
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
.map((c) => c.toLowerCase())
.join('_')

toSnakeCase('helloWorld') // hello_world
toSnakeCase('hello-world') // hello_world

const toKebabCase = (str) =>
str &&
str
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
.map((l) => l.toLowerCase())
.join('-')

toKebabCase('helloWorld') // hello-world
toKebabCase('hello world') // hello-world

Все возможные варианты строки

const getStrPermutations = (str) => {
if (str.length <= 2) return str.length === 2 ? [str, str[1] + str[0]] : [str]

return str
.split('')
.reduce(
(a, l, i) =>
a.concat(getStrPermutations(str.slice(0, i) + str.slice(i + 1)).map((v) => l + v)),
[]
)
}

getStrPermutations('abc') // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']

Чувствительная к языку сортировка строки

const sortStr = (str) => [...str].sort((a, b) => a.localeCompare(b)).join('')

sortStr('cabbage') // aabbceg
// or
const collator = new Intl.Collator()
const sortStr = (str) =>
[...str].sort((a, b) => collator.compare(a, b)).join('')

"Плюрализация" строки

const pluralize = (val, word, plural = word + 's') => {
const p = (num, word, plural = word + 's') =>
[1, -1].includes(Number(num)) ? word : plural
if (typeof val === 'object') return (num, word) => p(num, word, val[word])
return p(val, word, plural)
}

pluralize(1, 'apple') // 'apple'
pluralize(2, 'apple') // 'apples'
pluralize(2, 'person', 'people') // 'people'

Перенос строки по количеству указанных символов

const wordWrap = (str, max, br = '\n') =>
str.replace(
new RegExp(`(?![^\\n]{1,${max}}$)([^\\n]{1,${max}})\\s`, 'g'),
'$1' + br
)

wordWrap(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tempus.',
32
)
/*
Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Fusce tempus.
*/

Удаление всех лишних пробелов

const noSpace = (str) => str.trim().replace(/\s{2,}/g, ' ')
noSpace(' hello world ') // 'hello world'

Размер строки в байтах

const getByteSize = (str) => new Blob([str]).size

byteSize('😀') // 4
byteSize('Hello World') // 11

Наличие слов в строке

const ransomNote = (note, magazine) =>
note
.split(' ')
.every((word) =>
magazine.includes(word) ? (magazine = magazine.replace(word, '')) : false
)

const words = 'The quick brown fox jumps over the lazy dog'
ransomNote('lazy dog', words) // true
ransomNote('red fox', words) // false

Массив

Извлечение i-того элемента

const getNthItem = (arr, n = 0) =>
(n === -1 ? arr.slice(n) : arr.slice(n, n + 1))[0]

getNthItem([1, 2, 3], 1) // 2
getNthItem(['a', 'b', 'c'], -3) // a

Извлечение каждого i-того элемента

const getEveryNthItem = (arr, n) => arr.filter((_, i) => i % n === n - 1)

getEveryNthItem([1, 2, 3, 4, 5, 6], 2) // [2, 4, 6]

Создание

const createArr = (n, r) => new Array(n).fill(r)

createArr(5, '😃') //  ['😃', '😃', '😃', '😃', '😃']

// or
const createArr = (n) => Array.from({ length: n }, (_, i) => i)

createArr(5) // [0, 1, 2, 3, 4]

// or
const createArr = (n, fn) => Array.from({ length: n }, fn)

createArr(10, () => +Math.random().toFixed(2))
// [0.84, 0.21, 0.43, 0.24, 0.94, 0.2, 0.91, 0.44, 0.38, 0.28]

// or
const initArrWithRange = (end, start = 0, step = 1) =>
Array.from(
{ length: ~~((end - start + 1) / step) },
(_, i) => i * step + start
)

initArrWithRange(5) // [0, 1, 2, 3, 4, 5]
initArrWithRange(7, 3) // [3, 4, 5, 6, 7]
initArrWithRange(9, 0, 2) // [0, 2, 4, 6, 8]

// or
const initArrWithRange = (min, max, n = 1) =>
Array.from({ length: n }, () => ~~(Math.random() * (max - min + 1)) + min)

initArrWithRange(12, 35, 10)
// [ 34, 14, 27, 17, 30, 27, 20, 26, 21, 14 ]

Получение всех индексов элемента

const getAllIndexes = (arr, v) =>
arr.reduce((a, c, i) => (c === v ? [...a, i] : a), [])

getAllIndexes([1, 2, 3, 1, 2, 3], 1) // [0, 3]
getAllIndexes([1, 2, 3], 4) // []

Все возможные варианты

const getArrPermutations = (arr) => {
if (arr.length <= 2) return arr.length === 2 ? [arr, [arr[1], arr[0]]] : arr

return arr.reduce(
(a, c, i) =>
a.concat(
getArrPermutations([...arr.slice(0, i), ...arr.slice(i + 1)]).map((v) => [
c,
...v
])
),
[]
)
}

getArrPermutations([1, 2, 3])
// [ [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1] ]

Сравнение массивов

const areEqual = (a, b) => {
if (a.length !== b.length) return false
const _a = a.sort()
const _b = b.sort()
return _a.every((v, i) => v === _b[i])
}

const x = [1, 3, 5, 7, 9]
const y = [5, 3, 1, 9, 7]

areEqual(x, y) // true

Удаление элементов, удовлетворяющих условию - функции

const removeItems = (fn, arr) => arr.filter((...args) => !fn(...args))

removeItems((n) => n % 2 === 0, [1, 2, 3, 4, 5]) // [1, 3, 5]

removeItems((s) => s.length > 4, ['Apple', 'Pear', 'Kiwi', 'Banana'])
// ['Pear', 'Kiwi']

Перемешивание

const shuffle = (arr) => arr.toSorted(() => Math.random() - 0.5)

// тасование Фишер-Йетса
const shuffle = (arr) => {
const _arr = structuredClone(arr)
let l = arr.length
while (l) {
const i = ~~(Math.random() * l--);
[_arr[l], _arr[i]] = [_arr[i], _arr[l]]
}
return _arr
}

Процент элементов, удовлетворяющих условию

const percent = (arr, v) =>
(100 * arr.reduce((a, c) => a + (c < v ? 1 : 0) + (c === v ? 0.5 : 0), 0)) / arr.length

percent([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 6) // 55

Количество вхождений элемента

const getCountOccur = (arr, v) =>
arr.reduce((a, c) => (c === v ? a + 1 : a), 0)

getCountOccur([1, 1, 2, 1, 2, 3], 1) // 3

Количество вхождений каждого элемента

const getOccurCount = (arr) =>
arr.reduce((a, k) => {
a[k] = a[k] ? a[k] + 1 : 1
return a
}, {})

getOccurCount(['a', 'b', 'c', 'b', 'b', 'a'])
// { a: 2, b: 3, c: 1 }

Пересечение (одинаковые элементы) массивов

const intersect = (a, b) => a.filter((i) => b.includes(i))

intersect([1, 2, 3], [1, 2, 4]) // [1, 2]

Удаление указанных элементов

const removeItems = (arr, ...args) => arr.filter((i) => !args.includes(i))

removeItems([2, 1, 3], 1, 2) // [3]

// or
const pullItems = (arr, ...args) => {
const state = Array.isArray(args[0]) ? args[0] : args
const pulled = arr.filter((i) => !state.includes(i))
arr.length = 0
pulled.forEach((i) => arr.push(i))
return arr
}

const arr = ['a', 'b', 'c', 'a', 'b', 'c']

pullItems(arr, 'a', 'c') // [ 'b', 'b' ]

Стабильная сортировка

const stableSort = (arr, fn) =>
arr
.map((i, _i) => ({ i, _i }))
.sort((a, b) => fn(a.i, b.i) || a._i - b._i)
.map(({ i }) => i)

stableSort([2, 10, 20, 1]) // 1, 2, 10, 20

Фильтрация объектов

const reducedFilter = (arr, keys, fn) =>
arr.filter(fn).map((el) =>
keys.reduce((a, k) => {
a[k] = el[k]
return a
}, {})
)

const data = [
{
id: 1,
name: 'John',
age: 23
},
{
id: 2,
name: 'Jane',
age: 32
}
]

reducedFilter(data, ['id', 'name'], (i) => i.age > 23)
// [ { id: 2, name: 'Jane'} ]

Добавление элементов по указанному индексу (модификация splice)

const insertAt = (arr, i, ...args) => {
arr.splice(i + 1, 0, ...args.flat())
return arr
}

insertAt([1, 2, 3], 1, [4, 5]) // [1, 2, 4, 5, 3]

Среднее значение объектов

const averageBy = (arr, fn) =>
arr
.map(typeof fn === 'function' ? fn : (val) => val[fn])
.reduce((a, c) => a + c, 0) / arr.length

averageBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], (x) => x.n) // 5
averageBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], 'n') // 5

Сжатие (объединение) массивов

const zipArr = (...args) => {
const max = Math.max(...args.map((i) => i.length))
return Array.from({ length: max }).map((_, i) =>
Array.from({ length: args.length }, (_, j) => args[j][i])
)
}

zipArr(['a', 'b'], [1, 2], [true, false])
// [ ['a', 1, true], ['b', 2, false] ]

Разделение на части

// n - количество элементов каждой части
const chunk = (arr, n) => (arr.length > n ? [arr, arr.splice(n)] : arr)

chunk([1, 2, 3, 4, 5, 6, 7], 4) // [ [1, 2, 3, 4], [5, 6, 7] ]

// n - количество частей, на которые делится массив
const chunk = (arr, n) => {
const size = ~~(arr.length / n)
return Array.from({ length: n }, (_, i) =>
arr.slice(i * size, i * size + size)
)
}

chunk([1, 2, 3, 4, 5, 6, 7], 4) // [ [1, 2], [3, 4], [5, 6], [7] ]

Выборка элементов

// не удовлетворяющих условию - функции
const takeUntil = (arr, fn) => {
for (const [i, v] of arr.entries()) {
if (fn(v)) {
return arr.slice(0, i)
}
}
return arr
}

takeUntil([1, 2, 3, 4], (n) => n >= 3) // [1, 2]

// удовлетворяющих условию - функции
const dropWhile = (arr, fn) => {
while (arr.length > 0 && !fn(arr[0])) {
arr = arr.slice(1)
}
return arr
}

dropWhile([1, 2, 3, 4], (n) => n >= 3) // [3, 4]

Наибольшее и наименьшее значение объекта

const maxBy = (arr, fn) =>
Math.max(...arr.map(typeof fn === 'function' ? fn : (val) => val[fn]))

maxBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], 'n') // 8

const minBy = (arr, fn) =>
Math.min(...arr.map(typeof fn === 'function' ? fn : (val) => val[fn]))

minBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], 'n') // 2

Объединение массивов по условию - функции

const unionBy = (a, b, fn) => {
const s = new Set(a.map(fn))
return Array.from(new Set([...a, ...b.filter((x) => !s.has(fn(x)))]))
}

unionBy([2.1], [1.2, 2.3], Math.floor) // [2.1, 1.2]
unionBy([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 3 }], (x) => x.id)
// [{ id: 1 }, { id: 2 }, { id: 3 }]

Сортировка и объединение массивов

const sortAndMerge = (a, b) => {
const _a = [...a]
const _b = [...b]

return Array.from({ length: _a.length + _b.length }, () => {
if (!_a.length) return _b.shift()
else if (!_b.length) return _a.shift()
else return _a[0] > _b[0] ? _b.shift() : _a.shift()
})
}

sortAndMerge([1, 3, 5], [2, 4, 6]) // [1, 2, 3, 4, 5, 6]

Сортировка и объединение объектов по условию - ключу объекта

const combine = (a, b, p) =>
Object.values(
[...a, ...b].reduce((a, v) => {
if (v[p])
a[v[p]] = a[v[p]]
? { ...a[v[p]], ...v }
: { ...v }
return a
}, {})
)

const x = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]
const y = [{ id: 1, age: 28 }, { id: 3, age: 26 }, { age: 24 }]

combine(x, y, 'id')
/*
[
{ id: 1, name: 'John', age: 28 },
{ id: 2, name: 'Jane' },
{ id: 3, age: 26 }
]
*/

Суммирование значений объектов

const sumBy = (arr, fn) =>
arr.map(typeof fn === 'function')
? fn
: (v) => v[fn].reduce((a, c) => a + c, 0)

sumBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], (x) => x.n) // 20
sumBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], 'n') // 20

Группировка элементов по условию - функции

// значение - количество элементов
const countBy = (arr, fn) =>
arr.map(typeof fn === 'function' ? fn : (v) => v[fn]).reduce((a, v) => {
a[v] = (a[v] || 0) + 1
return a
}, {})

countBy([6.1, 4.2, 6.3], Math.floor) // { 4: 1, 6: 2 }
countBy(['one', 'two', 'three'], 'length') // { 3: 2, 5: 1 }

// значение - массив элементов
const groupBy = (arr, fn) =>
arr.map(typeof fn === 'function' ? fn : (v) => v[fn]).reduce((a, v, i) => {
a[v] = (a[v] || []).concat(arr[i])
return a
}, {})

groupBy([6.1, 4.2, 6.3], Math.floor) // {4: [4.2], 6: [6.1, 6.3]}
groupBy(['one', 'two', 'three'], 'length') // {3: ['one', 'two'], 5: ['three']}

Разделение массива по условию - функции

// [ [элементы, удовлетворяющие условию], [другие элементы] ]
const bifurcateBy = (arr, fn) =>
arr.reduce((acc, val, i) => (acc[fn(val, i) ? 0 : 1].push(val), acc), [
[],
[]
])

bifurcateBy(['foo', 'bar', 'baz'], (x) => x[0] === 'b')
// [ ['bar', 'baz'], ['foo'] ]

Определение разницы между массивами по условию - функции

const differenceBy = (x, y, fn) => {
const _x = new Set(x.map((i) => fn(i))),
_y = new Set(y.map((i) => fn(i)))
return [
...x.filter((i) => !_y.has(fn(i))),
...y.filter((i) => !_x.has(fn(i)))
]
}

differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor) // [1.2, 3.4]

differenceBy(
[{ id: 1 }, { id: 2 }, { id: 3 }],
[{ id: 1 }, { id: 2 }, { id: 4 }],
(i) => i.id
) // [ { id: 3 }, { id: 4 } ]

Сортировка объектов по условиям - ключам объекта в виде массива

const orderBy = (arr, props, orders) =>
[...arr].sort((x, y) =>
props.reduce((a, p, i) => {
if (a === 0) {
const [p1, p2] =
orders && orders[i] === 'desc' ? [y[p], x[p]] : [x[p], y[p]]
a = p1 > p2 ? 1 : p1 < p2 ? -1 : 0
}
return a
}, 0)
)

const users = [
{ name: 'John', age: 23 },
{ name: 'Jane', age: 22 },
{ name: 'Alice', age: 24 },
{ name: 'Bob', age: 21 }
]

orderBy(users, ['name', 'age'])
/*
[
{name: "Alice", age: 24},
{name: "Bob", age: 21},
{name: "Jane", age: 22},
{name: "John", age: 23}
]
*/

Сортировка объектов по ключу и в порядке значений

const orderWith = (arr, prop, order) => {
const ordered = order.reduce((a, c, i) => {
a[c] = i
return a
}, {})
return [...arr].sort((a, b) => {
if (ordered[a[prop]] === undefined) return 1
if (ordered[b[prop]] === undefined) return -1
return ordered[a[prop]] - ordered[b[prop]]
})
}

const users = [
{ name: 'john', language: 'JavaScript' },
{ name: 'jane', language: 'TypeScript' },
{ name: 'alice', language: 'JavaScript' },
{ name: 'bob', language: 'Java' },
{ name: 'igor' },
{ name: 'harry', language: 'Python' }
]

orderWith(users, 'language', ['JavaScript', 'TypeScript', 'Java'])
/*
[
{ name: 'john', language: 'JavaScript' },
{ name: 'alice', language: 'JavaScript' },
{ name: 'jane', language: 'TypeScript' },
{ name: 'bob', language: 'Java' },
{ name: 'igor' },
{ name: 'harry', language: 'Python' }
]
*/

Объект

Является ли объект пустым?

const isEmpty = (val) => val === null || !(Object.keys(val) || val).length

isEmpty('') // true
isEmpty([]) // true
isEmpty(['']) // false
isEmpty({ a: 1 }) // false

Размер итерируемого объекта

const getSize = (val) =>
val ? val.length || val.size || Object.keys(val).length || 0 : 0

getSize([1, 2, 3]) // 3
getSize('size') // 4
getSize(new Set([3, 3, 2, 2, 1])) // 3
getSize({ one: 1, two: 2, three: 3 }) // 3
getSize() // 0

Извлечение значений по селекторам

const getValues = (from, ...selectors) =>
[...selectors].map((s) =>
s
.replace(/\[([^\[\]]*)\]/g, '.$1.')
.split('.')
.filter((v) => v !== '')
.reduce((a, c) => a && a[c], from)
)

const obj = {
selector: { to: { val: 'value to select' } },
target: [1, 2, { a: 'test' }]
}

getValues(obj, 'selector.to.val', 'target[0]', 'target[2].a')
// ["value to select", 1, "test"]

Копирование

const copyObj = (obj, shallow = true) =>
shallow ? { ...obj } : JSON.parse(JSON.stringify(obj))

const obj = {
foo: 'bar',
baz: {
qux: {
a: 'b'
}
}
}

const _obj = copyObj(obj)
_obj.baz.qux.a = 'c'

obj.baz.qux.a // c

const __obj = copyObj(_obj, false)
__obj.baz.qux.a = 'd'

_obj.baz.qux.a // c

Рекурсивное (глубокое) копирование объекта (массива)

const deepClone = (obj) => {
if (obj === null) return null
const clone = Object.assign({}, obj)
Object.keys(clone).forEach(
(key) =>
(clone[key] =
typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key])
)
if (Array.isArray(obj)) {
clone.length = obj.length
return Array.from(clone)
}
return clone
}

const a = { foo: 'bar', obj: { a: 1, b: 2 } }
const b = deepClone(a) // a !== b, a.obj !== b.obj

Удаление свойств по условию - массиву ключей или функции

const omitBy = (obj, arr) =>
Object.keys(obj)
.filter((k) => !arr.includes(k))
.reduce((a, k) => ((a[k] = obj[k]), a), {})

omitBy({ a: 1, b: '2', c: 3 }, ['b']) // { a: 1, c: 3 }

const omitBy = (obj, fn) =>
Object.keys(obj)
.filter((k) => fn(obj[k], k))
.reduce((a, k) => ((a[k] = obj[k]), a), {})

omitBy({ a: 1, b: '2', c: 3 }, (x) => typeof x === 'number')
// { a: 1, c: 3 }

Извлечение свойств по условию - массиву ключей или функции

const pickBy = (obj, arr) =>
arr.reduce((a, k) => (k in obj && (a[k] = obj[k]), a), {})

pickBy({ a: 1, b: '2', c: 3 }, ['a', 'c']) // { a: 1, c: 3 }

const pickBy = (obj, fn) =>
Object.keys(obj)
.filter((k) => !fn(obj[k], k))
.reduce((a, k) => ((a[k] = obj[k]), a), {})

pickBy({ a: 1, b: '2', c: 3 }, (x) => typeof x === 'number')
// { b: '2' }

Извлечение глубоко вложенного свойства

const getDeepVal = (obj, key) =>
key in obj
? obj[key]
: Object.values(obj).reduce((acc, val) => {
if (acc !== undefined) return acc
if (typeof val === 'object') return getDeepVal(val, key)
}, undefined)

const obj = {
foo: {
bar: {
baz: 'qux'
}
}
}

getDeepVal(obj, 'baz') // qux
getDeepVal(obj, 'qux') // undefined

Извлечение свойства по условию - функции

const getKey = (obj, fn) =>
Object.keys(obj).find((key) => fn(obj[key], key, obj))

getKey(
{
john: { age: 23, active: true },
jane: { age: 24, active: false },
alice: { age: 25, active: true }
},
(x) => x['active']
)
// john

Извлечение ключей по значению

const getKeys = (obj, val) =>
Object.keys(obj).filter((key) => obj[key] === val)

const ages = {
John: 20,
Jane: 22,
Alice: 20
}

getKeys(ages, 20) // ['John', 'Alice']

Объединение объектов

const mergeObj = (...objects) =>
[...objects].reduce(
(acc, obj) =>
Object.keys(obj).reduce((a, k) => {
a[k] = a.hasOwnProperty(k) ? [].concat(a[k]).concat(obj[k]) : obj[k]
return acc
}, {}),
{}
)

const x = {
a: [{ x: 2 }, { y: 4 }],
b: 1
}

const y = {
a: { z: 3 },
b: [2, 3],
c: 'foo'
}

mergeObj(x, y)
/*
{
a: [ { x: 2 }, { y: 4 }, { z: 3 } ],
b: [ 1, 2, 3 ],
c: 'foo'
}
*/

Преобразование объекта в строку запроса

const convertObjToQuery = (params) =>
params
? Object.entries(params).reduce((str, [k, v]) => {
const s = str.length === 0 ? '?' : '&'
str += typeof v === 'string' ? `${s}${k}=${v}` : ''
return str
}, '')
: ''

convertObjToQuery({ page: '1', sort: 'desc', order: 'title' }) // ?page=1&sort=desc&order=title

Глубокая "заморозка" (обеспечение иммутабельности)

const deepFreeze = (obj) => {
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'object') deepFreeze(obj[key])
})
return Object.freeze(obj)
}

Сжатие (объединение) объектов

const zipObj = (props, vals) =>
props.reduce((obj, prop, i) => ((obj[prop] = vals[i]), obj), {})

zipObj(['a', 'b', 'c'], [1, 2]) // { a: 1, b: 2, c: undefined }
zipObj(['a', 'b'], [1, 2, 3]) // { a: 1, b: 2 }

Сравнение объектов

const areEqual = (a, b) => {
if (a === b) return true

if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()

if (!a || !b || (typeof a !== 'object' && typeof b !== 'object'))
return a === b

if (a.prototype !== b.prototype) return false

const keys = Object.keys(a)

if (keys.length !== Object.keys(b).length) return false

return keys.every((k) => areEqual(a[k], b[k]))
}

areEqual(
{ a: [2, { e: 3 }], b: [4], c: 'foo' },
{ a: [2, { e: 3 }], b: [4], c: 'foo' }
) // true
areEqual([1, 2, 3], { 0: 1, 1: 2, 2: 3 }) // true

Поиск совпадения по условию - функции

const matchesWith = (obj, source, fn) =>
Object.keys(source).every((key) =>
obj.hasOwnProperty(key) && fn
? fn(obj[key], source[key], key, obj, source)
: obj[key] === source[key]
)

const isGreet = (val) => /^h(?:i|ello)$/.test(val)
matchesWith(
{ greet: 'hello' },
{ greet: 'hi' },
(a, b) => isGreet(a) && isGreet(b)
) // true

Обеспечение возможности создания глубоко вложенных свойств

const deepProp = (obj) =>
new Proxy(obj, {
get: (target, prop) => {
if (!(prop in target)) target[prop] = deepProp({})
return target[prop]
}
})

const obj = deepProp({})

obj.x.y.z = 'foo'

console.log(obj.x.y.z) // foo

Создание итерируемого объекта

const makeIterable = (obj) => {
Object.defineProperties(obj, {
length: {
value: Object.keys(obj).length
},

[Symbol.iterator]: {
value: function* () {
for (const i in this) {
yield this[i]
}
}
}
})

return obj
}

const iterableObj = makeIterable({
name: 'Jane',
age: 23
})

for (const v of iterableObj) console.log(v) // Jane 23
console.log(...iterableObj) // Jane 23

Создание объекта, открытого для расширения, но закрытого для модификации

const makeOpenClosed = (obj) =>
new Proxy(obj, {
get: (target, key) => target[key],
set: (target, key, value) => {
if (target.hasOwnProperty(key)) {
throw new Error('error')
}
target[key] = value
}
})

const person = makeOpenClosed({ name: 'John', age: 30 })

console.log(person.name) // John
person.sex = 'm'
person.age = 20 // error

Функция

Искусственная задержка

const sleep = (ms) => new Promise((r) => setTimeout(r, ms))

const printNums = async () => {
console.log(1)
await sleep(1000)
console.log(2)
await sleep(1000)
console.log(3)
}

printNums()
// 1 2 3 с задержкой в 1 сек между каждым `console.log`

Измерение времени выполнения функции

const howLong = async (fn) => {
console.time('t')
const r = await fn()
console.timeEnd('t')
return r
}

howLong(printNums) // примерно 2 сек

Выполнение функции только при удовлетворении условия

const makeWhen = (fn, when) => (x) => fn(x) ? when(x) : x

const doubleWhenEven = makeWhen(
(n) => !(n & 1),
(n) => n * 2
)

doubleWhenEven(2) // 4
doubleWhenEven(1) // 1

Определение типа переданного значения

const getType = (v) =>
v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name

getType([]) // Array
getType(() => {}) // Function

// or
const getType = (v) => Object.prototype.toString.call(v).slice(8, -1)

getType({}) // Object
getType(function () {}) // Function

Асинхронное выполнение функции с помощью веб-воркера

const runAsync = (fn) => {
const worker = new Worker(
URL.createObjectURL(new Blob([`postMessage((${fn})())`]), {
type: 'application/javascript; charset=utf-8'
})
)
return new Promise((res, rej) => {
worker.onmessage = ({ data }) => {
res(data)
worker.terminate()
}
worker.onerror = (err) => {
rej(err)
worker.terminate()
}
})
}

const expensiveFn = () => {
let res = 0
for (let i = 0; i < 4000; i++)
for (let j = 0; j < 700; j++)
for (let k = 0; k < 300; k++)
res = res + i + j + k

return res
}

runAsync(expensiveFn).then(console.log)
// 2098740000000

Последовательное выполнение промисов

const runPromisesInSeq = (promises) =>
promises.reduce((prev, next) => prev.then(next), Promise.resolve())

const sleep = (ms) =>
new Promise((r) => {
const id = setTimeout(() => {
console.log(ms)
r()
clearTimeout(id)
}, ms)
})

runPromisesInSeq([() => sleep(1000), () => sleep(2000)])

Определение самой производительной функции

const getMostPerformFn = (fns, iter = 10) => {
const times = fns.map((fn) => {
const start = performance.now()

for (let i = 0; i < iter; i++) fn()

return performance.now() - start
})

const bestTime = Math.min(...times)
const bestFn = fns[times.indexOf(bestTime)]

return [Math.round(bestTime), bestFn]
}

const functions = [
() => quickSort(shuffledArr),
() => countingSort(shuffledArr),
() => nativeSort(shuffledArr)
]

const [bestTime, bestFn] = getMostPerformFn(functions)
console.log(bestTime, bestFn)

Однократное выполнение функции

const callOnce = (fn) => {
let wasCalled = false
return function (...args) {
if (wasCalled) return
wasCalled = true
return fn.apply(this, args)
}
}

Выполнение функции указанное количество раз

const callTimes = (n, fn, ctx = undefined) => {
let i = 0
while (fn.call(ctx, i) !== false && ++i < n) {}
}

let str = ''
callTimes(5, (i) => (str += i))
console.log(str) // 012345

Последовательное выполнение функций

const pipe = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)))

const add5 = (x) => x + 5
const mult = (x, y) => x * y

const multAndAdd5 = pipe(mult, add5)
multAndAdd5(5, 2) // 15

Последовательное выполнение асинхронных функций

const chainAsync = (fns) => {
let i = 0
const last = fns[fns.length - 1]
const next = () => {
const fn = fns[i++]
fn === last ? fn() : fn(next)
}
next()
}

chainAsync([
(next) => {
console.log('hi!')
setTimeout(next, 1000)
},
(next) => {
console.log('how are you?')
setTimeout(next, 1000)
},
() => {
console.log('bye!')
}
])

"Привязка" функции к объекту (определение контекста метода)

const bindKey =
(obj, fn) =>
(...args) =>
fn.apply(obj, args)

const user = {
name: 'John',
age: 24
}

function greet(phrase) {
console.log(`${phrase} My name is ${this.name}. I'm ${this.age} years old.`)
}

bindKey(user, greet)('Hi!')
// Hi! My name is John. I'm 24 years old.

"Привязка" методов к объекту (определение контекста методов)

const bindAll = (obj) => {
for (const key in obj) {
if (typeof obj[key] === 'function') {
obj[key] = obj[key].bind(obj)
}
}
}

const view = {
label: '!',
click() {
console.log('clicked' + this.label)
}
}

bindAll(view)
window.addEventListener('click', view.click)

Объединение функций

const mergeFns =
(...fns) =>
(...args) =>
fns.map((fn) => fn.apply(null, args))

const minMax = mergeFns(Math.min, Math.max)

minMax(1, 2, 3, 4, 5) // [1, 5]

"Промисификация"

const promisify = (fn) => (...args) =>
new Promise((resolve, reject) =>
fn(...args, (err, res) => (err ? reject(err) : resolve(res)))
)

const delay = promisify((d, cb) => setTimeout(cb, d))

delay(1000).then(() => {
console.log('Hi!')
})

"Мемоизация"

const memo = (fn) => {
const cache = new Map()

return (...args) => {
const key = args.sort().toString()

if (cache.has(key)) {
console.log('From cache')
return cache.get(key)
} else {
console.log('Calculated')
const res = fn(...args)
cache.set(key, res)
return res
}
}
}

const add = (x, y) => x + y
const memoAdd = memo(add)
memoAdd(24, 42) // Calculated
memoAdd(42, 24) // From cache

// another example
const fact = (n) => (n <= 1 ? 1 : n * fact(n - 1))

const memoFact = memo(fact)

howLong(() => memoFact(150)) // 0.15
howLong(() => memoFact(150)) // 0.05

// or
const memo = (fn) =>
new Proxy(fn, {
cache: new Map(),
apply(target, thisArg, args) {
const key = args.sort().toString()
let isCalculated = false

if (!this.cache.has(key)) {
this.cache.set(key, target.apply(thisArg, args))
isCalculated = true
}

console.log(isCalculated)
return this.cache.get(key)
}
})

const add = (x, y) => x + y
const memoAdd = memo(add)
memoAdd(24, 42) // true
memoAdd(42, 24) // false

Каррирование

// es5
function curry(a) {
return function (b) {
return function (c) {
return a + b + c
}
}
}

// es6
const curry = (a) => (b) => (c) => a + b + c

curryArrowSum(1) // b => c => 1 + b + c
curryArrowSum(1)(2) // c => 3 + c
curryArrowSum(1)(2)(3) // 6

// eval is evil
const curryCalcEval = (...nums) => (op) =>
nums.reduce((acc, cur) => (acc = eval(`acc ${op} cur`)))

curryCalcEval(1, 2, 3)('+') // 6

Создание "каррированной" функции

const currying = (fn) =>
function curry(...args) {
return fn.length <= args.length
? fn.apply(this, args)
: (...moreArgs) => curry.apply(this, args.concat(moreArgs))
}

const sum = currying((a, b, c, d) => a + b + c + d)

sum(1)(2)(3)(4) // 10
sum(3, 1)(2, 4) // 10

// or
const currying = (fn, arity = fn.length, ...args) =>
arity <= args.length ? fn(...args) : currying.bind(null, fn, arity, ...