На главную
/**
* Проблема чисел с плавающей точкой
*/
0.1 + 0.2 === 0.3 // false
const isEqual = (x, y) => x - y < Number.EPSILON
isEqual(0.1 + 0.2, 0.3) // true
// --- / --- / --- / --- //
/**
* Четность/нечетность числа
*/
// 1
const isEven = (n) => n % 2 === 0
// 2
const _isEven = (n) => !(n & 1)
isEven(1) // false
_isEven(4) // true
// 1
const isOdd = (n) => n % 2 !== 0
// 2
const _isOdd = (n) => !!(n & 1)
isOdd(1) // true
_isOdd(4) // false
// --- / --- / --- / --- //
/**
* Является ли число степенью числа 2?
*/
const isPowerOfTwo = (n) => !!n && (n & (n - 1)) === 0
isPowerOfTwo(3) // false
isPowerOfTwo(8) // true
// --- / --- / --- / --- //
/**
* Сумма чисел, входящих в число
*/
const sum = (n) => (n * (n + 1)) / 2
sum(100) // 5050
// --- / --- / --- / --- //
/**
* Делится ли одно число на другое без остатка?
*/
const isDivisible = (x, y) => x % y === 0
isDivisible(4, 2) // true
isDivisible(5, 3) // false
// --- / --- / --- / --- //
/**
* Находится ли число в заданном диапазоне?
*/
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}`
round(1.005, 2) // 1.01
// --- / --- / --- / --- //
/**
* Сохранение знака числа
*/
const saveSign = (x) => (!Math.sign(x) ? -x : x)
saveSign(-2) // -2
// --- / --- / --- / --- //
/**
* Частное и остаток
*/
const divMod = (x, y) => [~~(x / y), x % y]
divMod(4, 2) // [2, 0]
divMod(5, 3) // [1, 2]
// --- / --- / --- / --- //
/**
* Ближайшая степень второго числа
*/
const nthRoot = (x, y) => Math.pow(x, 1 / y)
nthRoot(32, 5) // 2
// --- / --- / --- / --- //
/**
* Натуральные (простые) числа
*/
// 1
const primes = (num) => {
let arr = Array.from({ length: num - 1 }).map((_, i) => i + 2),
root = ~~Math.sqrt(num),
nums = Array.from({ length: root - 1 }).map((_, i) => i + 2)
nums.forEach((x) => (arr = arr.filter((y) => y % x !== 0 || y === x)))
return arr
}
primes(10) // [2, 3, 5, 7]
// Решето Эратосфена
// https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D1%88%D0%B5%D1%82%D0%BE_%D0%AD%D1%80%D0%B0%D1%82%D0%BE%D1%81%D1%84%D0%B5%D0%BD%D0%B0
const _primes = (num) => {
const arr = new Array(num + 1)
arr.fill(true)
arr[0] = arr[1] = false
for (let i = 2; i <= Math.sqrt(num); i++) {
for (let j = 2; i * j <= num; j++) {
arr[i * j] = false
}
}
return arr.reduce(
(primes, isPrime, prime) => (isPrime ? primes.concat(prime) : primes),
[]
)
}
_primes(10) // [2, 3, 5, 7]
// --- / --- / --- / --- //
/**
* Сложение и возведение в степень
*/
const sumPower = (end, power = 2, start = 1) =>
Array(end + 1 - start)
.fill(0)
.map((_, i) => (i + start) ** power)
.reduce((a, c) => a + c, 0)
sumPower(10) // 385
sumPower(10, 3) // 3025
sumPower(10, 3, 5) // 2925
// --- / --- / --- / --- //
/**
* Арифметическая прогрессия
*/
const arithmeticProgress = (n, max) =>
Array.from({ length: Math.ceil(max / n) }, (_, i) => (i + 1) * n)
arithmeticProgress(5, 25)
// [5, 10, 15, 20, 25]
// --- / --- / --- / --- //
/**
* Геометрическая прогрессия
*/
const geometricProgress = (end, start = 1, step = 2) =>
Array.from({ length: ~~(Math.log(end / start) / Math.log(step)) + 1 }).map(
(_, i) => start * step ** i
)
geometricProgress(256)
// [1, 2, 4, 8, 16, 32, 64, 128, 256]
geometricProgress(256, 1, 4)
// [1, 4, 16, 64, 256]
// --- / --- / --- / --- //
/**
* Наибольший общий делитель
*/
const gcd = (...arr) => {
const _gcd = (x, y) => (!y ? x : gcd(y, x % y))
return [...arr].reduce((a, b) => _gcd(a, b))
}
gcd(8, 36) // 4
gcd(...[12, 8, 32]) // 4
// --- / --- / --- / --- //
/**
* Наименьший общий множитель
*/
const lcm = (...args) => {
const gcd = (x, y) => (!y ? x : gcd(y, x % y))
const _lcm = (x, y) => (x * y) / gcd(x, y)
return [...args].reduce((a, b) => _lcm(a, b))
}
lcm(12, 7) // 84
lcm(...[1, 3, 4, 5]) // 60
// --- / --- / --- / --- //
/**
* Преобразование числа в массив
*/
const digitize = (n) => [...`${Math.abs(n)}`].map((i) => parseInt(i))
digitize(123) // [1, 2, 3]
// --- / --- / --- / --- //
/**
* Преобразование числа в валюту
*/
const toCurrency = (num, cur, lang = 'en-US') =>
Intl.NumberFormat(lang, {
style: 'currency',
currency: cur
}).format(num)
toCurrency(100, 'RUB', 'ru-RU') // 100,00 ₽
// --- / --- / --- / --- //
/**
* Преобразование арабских чисел в римские
*/
const toRomanNum = (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
}, '')
}
toRomanNumeral(3) // III
toRomanNumeral(11) // XI
// --- / --- / --- / --- //
/**
* Инверсия числа с сохранением знака
*/
const reverseNum = (n) =>
parseFloat([...`${n}`].reverse().join('')) * Math.sign(n)
reverseNum(981) // 189
reverseNum(-500) // -5
reverseNum(73.6) // 6.37
reverseNum(-5.23) // -32.5
// --- / --- / --- / --- //
/**
* Среднее значение
*/
const average = (...nums) => nums.reduce((a, c) => a + c, 0) / nums.length
average(...[1, 2, 3]) // 2
average(1, 2, 3) // 2
// --- / --- / --- / --- //
/**
* Числа Фибоначчи
* https://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%B0_%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8
*/
const fib = (n) => {
if (n < 0) throw new Error('error')
if (n <= 1) return n
let a = 1,
b = 1
for (let i = 3; i <= n; i++) {
let c = a + b
a = b
b = c
}
return b
}
fib(10) // 55
// --- / --- / --- / --- //
/**
* Факториал
* https://ru.wikipedia.org/wiki/%D0%A4%D0%B0%D0%BA%D1%82%D0%BE%D1%80%D0%B8%D0%B0%D0%BB
*/
const fact = (n) => {
if (n < 0) throw new Error('error')
if (n <= 1) return 1
return n * fact(n - 1)
}
fact(5) // 120
// --- / --- / --- / --- //
/**
* Случайное целое число в заданном диапазоне
*/
const getRandomInt = (min, max) => ~~(min + Math.random() * (max - min + 1))
// --- / --- / --- / --- //
/**
* Случайное положительное число (целое или дробное)
* в заданном диапазоне
*/
const getRandomPosiviteNum = (min, max, precision) => {
if (min < 0 || min <= max) throw new Error('error')
const n = min + Math.random() * (max - min + 1)
return !precision ? ~~n : n.toFixed(precision)
}
// --- / --- / --- / --- //
/**
* Преобразование радиан в градусы и обратно
*/
const radToDeg = (rad) => (rad * 180) / Math.PI
const degToRad = (deg) => (deg * Math.PI) / 180
// --- / --- / --- / --- //
/**
* Форматирование миллисекунд
*/
const formatMs = (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(', ')
}
formatMs(1001) // 1 second, 1 millisecond
formatMs(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(1000) // '1 KB'
formatBytes(-27145424323.5821, 5) // '-27.145 GB'
formatBytes(123456789, 3, false) // '123MB'
// --- / --- / --- / --- //
/**
* Приведение числа ко времени в двенадцатичасовом формате
*/
const getSuffixOfInt = (num) =>
num === 0 || num === 24
? 12 + 'am'
: num === 12
? 12 + 'pm'
: num < 12
? (num % 12) + 'am'
: (num % 12) + 'pm'
getSuffixOfInt(0) // '12am'
getSuffixOfInt(11) // '11am'
getSuffixOfInt(13) // '1pm'
getSuffixOfInt(25) // '1pm'
// --- / --- / --- / --- //
/**
* Маскировка чисел
*/
const mask = (num, n = 4, mask = '*') =>
`${num}`.slice(-n).padStart(`${num}`.length, mask)
mask(1234567890) // '******7890'
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
// --- / --- / --- / --- //
/**
* Расстояние Хэмминга
* https://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D1%81%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5_%D0%A5%D1%8D%D0%BC%D0%BC%D0%B8%D0%BD%D0%B3%D0%B0
*/
const hammingDistance = (num1, num2) =>
((num1 ^ num2).toString(2).match(/1/g) || '').length
hammingDistance(2, 3) // 1
// --- / --- / --- / --- //
/**
* Биномиальный коэффициент
* https://ru.wikipedia.org/wiki/%D0%91%D0%B8%D0%BD%D0%BE%D0%BC%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BE%D1%8D%D1%84%D1%84%D0%B8%D1%86%D0%B8%D0%B5%D0%BD%D1%82
*/
const binomialCoefficient = (n, k) => {
if (Number.isNaN(n) || Number.isNaN(k)) return NaN
if (k < 0 || k > n) return 0
if (k === 0 || k === n) return 1
if (k === 1 || k === n - 1) return n
if (n - k < k) k = n - k
let res = n
for (let j = 2; j <= k; j++) res *= (n - j + 1) / j
return Math.round(res)
}
binomialCoefficient(8, 2) // 28
/**
* Инверсия слова
*/
// 1
const reverseWord = (str) => [...str].reverse().join('')
reverseWord('foo') // oof
// 2
const _reverseWord = (str) => {
let res = ''
for (const char of str) res = char + res
return res
}
// 3
reverseWord('bar') // rab
const __reverse = (str) => [...str].reduce((res, char) => char + res)
__reverse('baz') // zab
// --- / --- / --- / --- //
/**
* Инверсия строки
*/
// только слов в строке
const reverseWords = (str) => str.split(' ').reverse().join(' ')
reverseWords('hello world') // world hello
// слов в строке и самих слов
const _reverseWords = (str) =>
str
.split(' ')
.map((w) => [...w].reverse().join(''))
.join(' ')
_reverseWords('hello world') // olleh dlrow
// --- / --- / --- / --- //
/**
* "Капитализация" строки
*/
// одного слова
// 1
const capitilizeWord = (str) => `${str[0].toUpperCase()}${str.slice(1)}`
capitilizeWord('foo') // Foo
// 2
const _capitilizeWord = ([first, ...rest]) =>
first.toUpperCase() + rest.join('').toLocaleLowerCase()
_capitilizeWord('bAr') // Bar
// дополнительный функционал
const __capitilizeWord = ([first, ...rest], lower = false) =>
first.toUpperCase() + (lower ? rest.join('').toLowerCase() : rest.join(''))
__capitilizeWord('fooBar') // 'FooBar'
__capitilizeWord('fooBar', true) // 'Foobar'
// строки
// 1
const capitilizeWords = (str) =>
str
.split(' ')
.map((w) => `${w[0].toUpperCase()}${w.slice(1)}`)
.join(' ')
capitilizeWords('foo bar') // Foo Bar
// 2
const _capitalizeWords = (str) =>
str.replace(/\b[a-zа-яё]/g, (w) => w.toUpperCase())
_capitalizeWords('hello world') // 'Hello World'
// --- / --- / --- / --- //
/**
* Сокращение строки
*/
// n - количество букв без ...
const truncateWord = (str, n) =>
str.length > n ? str.slice(0, n > 3 ? n : 3) + '...' : str
truncateWord('boomerang', 4) // boom...
// n - количество букв с ...
const _truncateWord = (str, n) =>
str.length > n ? str.substr(0, n - 3) + '...' : str
_truncateWord('JavaScript', 7) // Java...
// n - количество слов
const truncateWords = (str, n) => str.split(' ').slice(0, n).join(' ')
truncateWords('JavaScript is awesome!', 1) // JavaScript
// дополнительный функционал
const _truncateWords = (str, max, end = '...') => {
if (str.length <= max) return str
const last = str.slice(0, max - end.length + 1).lastIndexOf(' ')
return str.slice(0, last > 0 ? last : max - end.length) + end
}
_truncateWords('short', 10) // 'short'
_truncateWords('not so short', 10) // 'not so...'
_truncateWords('trying a thing', 10) // 'trying...'
_truncateWords('javascripting', 10) // 'javascr...'
// --- / --- / --- / --- //
/**
* Определение количества вхождений подстроки в строку
*/
const countSub = (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
}
}
countSub('tiktok tok tok tik 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
// --- / --- / --- / --- //
/**
* Являются ли строки анаграммами?
* (латиница и кириллица)
* https://ru.wikipedia.org/wiki/%D0%90%D0%BD%D0%B0%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B0
*/
// 1
const isAnagrams = (x, y) => {
const fn = (str) =>
str
.toLowerCase()
.replace(/[^a-zа-яё0-9]/gi, '')
.split('')
.sort()
.join('')
return fn(x) === fn(y)
}
isAnagrams('воз', 'зов') // true
// 2
const sort = (str) => [...str.replace(/\W/g, '').toLowerCase()].sort().join('')
const _isAnagrams = (a, b) => sort(a) === sort(b)
_isAnagrams('listen', 'silent') // true
// 3
const __isAnagrams = (x, y) =>
x.length === y.length &&
[...x.toLowerCase()].every((c) => y.toLowerCase().includes(c))
__isAnagrams('the eyes', 'they see') // true
// --- / --- / --- / --- //
/**
* Является ли строка палиндромом?
* (латиница и кириллица)
* https://ru.wikipedia.org/wiki/%D0%9F%D0%B0%D0%BB%D0%B8%D0%BD%D0%B4%D1%80%D0%BE%D0%BC
*/
// 1
const isPalindrome = (str) =>
[...str.toLowerCase().replace(/\W/g, '')].every(
(c, i) => c === str[str.length - 1 - i]
)
isPalindrome('А роза упала на лапу Азора') // true
// 2
const _isPalindrome = (str) => (
(str = str.toLowerCase().replace(/\W/g, '')),
str === [...str].reverse().join('')
)
_isPalindrome('Borrow or rob') // true
// --- / --- / --- / --- //
/**
* Все возможные варианты строки
*/
const permutStr = (str) => {
if (str.length <= 2) return str.length === 2 ? [str, str[1] + str[0]] : [str]
return str
.split('')
.reduce(
(a, l, i) =>
a.concat(permutStr(str.slice(0, i) + str.slice(i + 1)).map((v) => l + v)),
[]
)
}
permutStr('abc') // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']
// --- / --- / --- / --- //
/**
* Преобразование строки в массив
*/
const strToWords = (str, p = /[^a-zа-яё-]+/i) => str.split(p).filter(Boolean)
strToWords('Я люблю JavaScript!') // ['Я', 'люблю', 'JavaScript']
strToWords('JavaScript & TypeScript') // ['JavaScript', 'TypeScript']
// --- / --- / --- / --- //
/**
* Чувствительная к языку сортировка строки
*/
const sortStr = (str) => [...str].sort((a, b) => a.localeCompare(b)).join('')
sortStr('cabbage') // aabbceg
// --- / --- / --- / --- //
/**
* Единственное -> множественное число
* (латиница)
*/
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(0, 'apple') // 'apples'
pluralize(1, 'apple') // 'apple'
pluralize(2, 'apple') // 'apples'
pluralize(2, 'person', 'people') // 'people'
// --- / --- / --- / --- //
/**
* Полный хеш строки
*/
const sdbm = (str) =>
[...str].reduce(
(hash, cur) =>
(hash = cur.charCodeAt(0) + (hash << 6) + (hash << 16) - hash),
0
)
sdbm('name') // -3521204949
// --- / --- / --- / --- //
/**
* Перенос строки по количеству указанных символов
*/
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 singleOccur = (str) =>
[...str].find((c) => str.indexOf(c) === str.lastIndexOf(c))
singleOccur('bbaccc') // a
// символ с максимальным числом вхождений
const maxOccur = (str) => {
const obj = {}
for (const char of str) obj[char] = obj[char] + 1 || 1
return Object.keys(obj).find(
(c) => obj[c] === Math.max(...Object.values(obj))
)
}
maxOccur('bbaccc') // c
// --- / --- / --- / --- //
/**
* Количество гласных букв в слове
* (латиница и кириллица)
*/
const vowels = (str) => str.match(/[aeiouyауоиэыяюеё]/gi)?.length || 0
vowels('foo bAr привет народ') // 7
// --- / --- / --- / --- //
/**
* Определение наличия слов в строке
*/
// 1
const ransomNote = (note, magazine) => {
const hash = {}
magazine.split(' ').forEach((word) => {
if (!hash[word]) hash[word] = 0
hash[word]++
})
let possible = true
note.split(' ').forEach((word) => {
if (hash[word] && hash[word] > 0) {
hash[word]--
} else possible = false
})
return possible
}
const words = 'The quick brown fox jumps over the lazy dog'
ransomNote('quick fox', words) // true
ransomNote('big dog', words) // false
// 2
const _ransomNote = (note, magazine) =>
note
.split(' ')
.every((word) =>
magazine.includes(word) ? (magazine = magazine.replace(word, '')) : false
)
_ransomNote('lazy dog', words) // true
_ransomNote('red fox', words) // false
/**
* Извлечение последнего элемента
*/
// 1
const lastItem = (arr, len = arr.length) => (arr && len ? arr[len - 1] : undefined)
const arr = [1, 2, 3, 4]
lastItem(arr) // 4
// 2
const nthArg = (n) => (...args) => args.slice(n)[0]
const _lastItem = nthArg(-1)
_lastItem(...arr) // 4
// --- / --- / --- / --- //
/**
* Извлечение i-того элемента
*/
const nthItem = (arr, n = 0) =>
(n === -1 ? arr.slice(n) : arr.slice(n, n + 1))[0]
nthItem([1, 2, 3], 1) // 2
nthItem(['a', 'b', 'c'], -3) // a
// --- / --- / --- / --- //
/**
* Извлечение каждого i-того элемента
*/
const getEveryNth = (arr, n) => arr.filter((_, i) => i % n === n - 1)
getEveryNth([1, 2, 3, 4, 5, 6], 2) // [2, 4, 6]
// --- / --- / --- / --- //
/**
* Создание массива
*/
// 1
const createArr = (n) => Array.from({ length: n }, (_, i) => i)
createArr(5) // [0, 1, 2, 3, 4]
// 2
const _createArr = (n, fn) => Array.from({ length: n }, (_, i) => fn(i))
_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]
// дополнительный функционал
// 1
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]
// 2
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 allIndexes = (arr, val) =>
arr.reduce((acc, el, i) => (el === val ? [...acc, i] : acc), [])
allIndexes([1, 2, 3, 1, 2, 3], 1) // [0, 3]
allIndexes([1, 2, 3], 4) // []
// --- / --- / --- / --- //
/**
* Сумма двух чисел
*/
const twoSum = (arr, sum) => {
const pairs = []
for (const part1 of arr) {
const part2 = sum - part1
if (arr.includes(part2)) {
pairs.push([part1, part2])
arr.splice(arr.indexOf(part2, 1))
}
}
return pairs
}
twoSum([1, 2, 2, 3, 4], 4) // [ [1, 3], [2, 2] ]
// --- / --- / --- / --- //
/**
* Все возможные варианты массива
*/
const permutArr = (arr) => {
if (arr.length <= 2) return arr.length === 2 ? [arr, [arr[1], arr[0]]] : arr
return arr.reduce(
(acc, cur, i) =>
acc.concat(
permutArr([...arr.slice(0, i), ...arr.slice(i + 1)]).map((v) => [
cur,
...v
])
),
[]
)
}
permutArr([1, 2, 3])
// [ [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1] ]
// --- / --- / --- / --- //
/**
* Определение самого длинного элемента массива
*/
const longestItem = (...args) =>
args.reduce((a, b) => (b.length > a.length ? b : a))
longestItem('a', 'abc', 'ab') // abc
longestItem(...['abc', 'a', 'ab'], 'abcd') // abcd
longestItem([1, 2, 3], [1, 2], [1, 2, 3, 4]) // [1, 2, 3, 4]
// --- / --- / --- / --- //
/**
* Сравнение массивов
*/
const areEqual = (a, b) => {
if (a.length !== b.length) return false
const _a = a.sort(),
_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 isSubset = (x, y) => {
const _x = new Set(x),
_y = new Set(y)
return [..._x].every((i) => _y.has(i))
}
isSubset([1, 2, 3, 4], [2, 4]) // true
// --- / --- / --- / --- //
/**
* Можно ли объединить два массива без дубликатов?
*/
const isJoin = (a, b) => {
const _a = new Set(a),
_b = new Set(b)
return [..._a].every((i) => !_b.has(i))
}
isJoin([1, 2], [3, 4]) // true
isJoin([1, 2], [1, 3]) // false
// --- / --- / --- / --- //
/**
* Сдвиг на указанное количество элементов
*/
const offset = (arr, off) => [...arr.slice(off), ...arr.slice(0, off)]
offset([1, 2, 3, 4, 5], 2) // [3, 4, 5, 1, 2]
offset([1, 2, 3, 4, 5], -2) // [4, 5, 1, 2, 3]
// --- / --- / --- / --- //
/**
* Удаление элементов массива,
* удовлетворяющих условию - функции
*/
const rejectItems = (pred, arr) => arr.filter((...args) => !pred(...args))
rejectItems((n) => n % 2 === 0, [1, 2, 3, 4, 5]) // [1, 3, 5]
rejectItems((s) => s.length > 4, ['Apple', 'Pear', 'Kiwi', 'Banana'])
// ['Pear', 'Kiwi']
// --- / --- / --- / --- //
/**
* Перемешивание элементов массива -
* тасование Фишера — Йетса
* https://ru.wikipedia.org/wiki/%D0%A2%D0%B0%D1%81%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D0%B8%D1%88%D0%B5%D1%80%D0%B0_%E2%80%94_%D0%99%D0%B5%D1%82%D1%81%D0%B0
*/
const shuffleArr = ([...arr]) => {
let l = arr.length
while (l) {
const i = ~~(Math.random() * l--)
;[(arr[l], arr[i])] = [arr[i], arr[l]]
}
return arr
}
const foo = [1, 2, 3]
shuffleArr(foo) // -> [2, 3, 1] foo -> [1, 2, 3]
// --- / --- / --- / --- //
/**
* Определение процента элементов массива,
* удовлетворяющих условию
*/
const percent = (arr, val) =>
(100 *
arr.reduce((a, v) => a + (v < val ? 1 : 0) + (v === val ? 0.5 : 0), 0)) /
arr.length
percent([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 6) // 55
// --- / --- / --- / --- //
/**
* Определение количества вхождений элемента в массиве
*/
const countOccur = (arr, val) =>
arr.reduce((a, v) => (v === val ? a + 1 : a), 0)
countOccur([1, 1, 2, 1, 2, 3], 1) // 3
// --- / --- / --- / --- //
/**
* Определение одинаковых элементов двух массивов
*/
const identicalItems = (a, b) => a.filter((i) => b.includes(i))
identicalItems([1, 2, 3], [1, 2, 4]) // [1, 2]
// --- / --- / --- / --- //
/**
* Удаление указанных элементов
*/
const withoutItems = (arr, ...args) => arr.filter((i) => !args.includes(i))
withoutItems([2, 1, 3], 1, 2) // [3]
// --- / --- / --- / --- //
/**
* Стабильная сортировка
*/
const stableSort = (arr, compare) =>
arr
.map((i, _i) => ({ i, _i }))
.sort((a, b) => compare(a.i, b.i) || a._i - b._i)
.map(({ i }) => i)
stableSort([2, 10, 20, 1]) // 1, 2, 10, 20
// --- / --- / --- / --- //
/**
* Инверсия элементов массива без `reverse`
*/
// 1
const reverse = (arr) => {
for (let i = 0; i < array.length / 2; i++) {
;[arr[i], arr[arr.length - 1 - i]] = [arr[arr.length - 1 - i], arr[i]]
}
return arr
}
reverse([1, 2, 3]) // [3, 2, 1]
// 2
const _reverse = (arr) => {
const newArr = []
while (arr.length) {
newArr.push(...arr.splice(arr.length - 1))
}
return newArr
}
// --- / --- / --- / --- //
/**
* Фильтрация объектов в массиве
*/
const reducedFilter = (arr, keys, fn) =>
arr.filter(fn).map((el) =>
keys.reduce((acc, key) => {
acc[key] = el[key]
return acc
}, {})
)
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'} ]
// --- / --- / --- / --- //
/**
* Определение самого часто встречающегося элемента в массиве
*/
const mostFreq = (arr) =>
Object.entries(
arr.reduce((a, v) => {
a[v] = a[v] ? a[v] + 1 : 1
return a
}, {})
).reduce((a, v) => (v[1] >= a[1] ? v : a), [null, 0])[0]
mostFreq(['a', 'b', 'c', 'b', 'b', 'a']) // b
// --- / --- / --- / --- //
/**
* Определение количества вхождений каждого элемента в массиве
*/
const howFreq = (arr) =>
arr.reduce((a, k) => {
a[k] = a[k] ? a[k] + 1 : 1
return a
}, {})
howFreq(['a', 'b', 'c', 'b', 'b', 'a']) // { a: 2, b: 3, c: 1 }
// --- / --- / --- / --- //
/**
* Удаление элементов по условию - функции
* (возвращается массив удаленных элементов)
*/
const remove = (arr, fn) =>
Array.isArray(arr)
? arr.filter(fn).reduce((a, v) => {
arr.splice(arr.indexOf(v), 1)
return a.concat(v)
}, [])
: []
remove([1, 2, 3, 4], (n) => n % 2 === 0) // [2, 4]
// --- / --- / --- / --- //
/**
* Цикличный генератор
*/
const cycleGen = function* (arr) {
let i = 0
while (true) {
yield arr[i % arr.length]
i++
}
}
const binaryCycle = cycleGenerator([0, 1])
binaryCycle.next() // { value: 0, done: false }
binaryCycle.next() // { value: 1, done: false }
binaryCycle.next() // { value: 0, done: false }
binaryCycle.next() // { value: 1, done: false }
// --- / --- / --- / --- //
/**
* Добавление элементов по указанному индексу
* (модицикация `splice`)
*/
const insertAt = (arr, i, ...args) => {
arr.splice(i + 1, 0, ...args)
return arr
}
insertAt([1, 2, 3], 1, 4, 5) // [1, 2, 4, 5, 3]
// --- / --- / --- / --- //
/**
* Удаление всех указанных элементов
*/
const pull = (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']
pull(arr, 'a', 'c') // [ 'b', 'b' ]
// --- / --- / --- / --- //
/**
* Удаление элементов по индексам
*/
const pullAtIndex = (arr, arrToPull) => {
const removed = []
const pulled = arr
.map((item, index) =>
arrToPull.includes(index) ? removed.push(item) : item
)
.filter((_, index) => !arrToPull.includes(index))
arr.length = 0
pulled.forEach((item) => arr.push(item))
return arr
}
const arr = ['a', 'b', 'c', 'd']
pullAtIndex(arr, [1, 3])
// arr -> [ 'a', 'c' ] removed -> [ 'b', 'd' ]
// --- / --- / --- / --- //
/**
* Среднее значение объектов массива
*/
const averageBy = (arr, fn) =>
arr
.map(typeof fn === 'function' ? fn : (v) => v[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 = (...arrs) => {
const max = Math.max(...arrs.map((i) => i.length))
return Array.from({ length: max }).map((_, i) =>
Array.from({ length: arrs.length }, (_, k) => arrs[k][i])
)
}
zipArr(['a', 'b'], [1, 2], [true, false])
// [ ['a', 1, true], ['b', 2, false] ]
// --- / --- / --- / --- //
/**
* Определение наибольшего и наименьшего элементов
* без `Math.max` и `Math.min`
*/
// наибольший элемент
// 1
const max = (arr) => {
let len = arr.length
let max = -Infinity
while (len--) {
if (arr[len] > max) {
max = arr[len]
}
}
return max
}
max([3, 2, 4]) // 4
// 2
const _max = (arr, n = 1) => [...arr].sort((x, y) => y - x).slice(0, n)
_max([1, 2, 3]) // [3]
_max([1, 2, 3], 2) // [3, 2]
// наименьший элемент
// 1
const min = (arr) => {
let len = arr.length
let min = Infinity
while (len--) {
if (arr[len] < min) {
min = arr[len]
}
}
return min
}
min([3, 2, 4]) // 2
// 2
const _min = (arr, n = 1) => arr.sort((x, y) => x - y).slice(0, n)
_min([1, 2, 3]) // [1]
_min([1, 2, 3], 2) // [1, 2]
// --- / --- / --- / --- //
/**
* Определение наибольшего и наименьшего значений объектов в массиве
*/
// наибольшее значение
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 mergeSortArrs = (a, b) => {
const _a = [...a],
_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()
})
}
mergeSortArrs([1, 3, 5], [2, 4, 6]) // [1, 2, 3, 4, 5, 6]
// --- / --- / --- / --- //
/**
* Сортировка и объединение массивов объектов
* по условию (ключу объекта)
*/
const combine = (a, b, prop) =>
Object.values(
[...a, ...b].reduce((acc, val) => {
if (val[prop])
acc[val[prop]] = acc[val[prop]]
? { ...acc[val[prop]], ...val }
: { ...val }
return acc
}, {})
)
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 }
]
*/
// --- / --- / --- / --- //
/**
* Распаковка массива
* (рекурсивная реализация `flat`)
*/
const flatBy = (arr, dep = 1) =>
arr.reduce(
(a, v) => a.concat(dep > 1 && Array.isArray(v) ? flatBy(v, dep - 1) : v),
[]
)
flatBy([1, [2], 3, 4]) // [1, 2, 3, 4]
flatBy([1, [2, [3, [4, 5], 6], 7], 8], 2) // [1, 2, 3, [4, 5], 6, 7, 8]
// --- / --- / --- / --- //
/**
* Проверка уникальности элементов
* с помощью условия - функции
*/
const uniqueBy = (arr, fn) => arr.length === new Set(arr.map(fn)).size
uniqueBy([1.2, 2.4, 2.9], Math.round) // true
uniqueBy([1.2, 2.3, 2.4], Math.round) // false
// --- / --- / --- / --- //
/**
* Выборка элементов
*/
// дублирующихся
const filterUnique = (arr) =>
[...new Set(arr)].filter((i) => arr.indexOf(i) !== arr.lastIndexOf(i))
filterUnique([1, 2, 2, 3, 4, 4, 5]) // [2, 4]
// уникальных
const filterNonUnique = (arr) =>
[...new Set(arr)].filter((i) => arr.indexOf(i) === arr.lastIndexOf(i))
filterNonUnique([1, 2, 2, 3, 4, 4, 5]) // [1, 3, 5]
// --- / --- / --- / --- //
/**
* Выборка объектов в массиве по условию - функции
*/
// уникальных
const filterNonUniqueBy = (arr, fn) =>
arr.filter((v, i) => arr.every((x, j) => (i === j) === fn(v, x, i, j)))
filterNonUniqueBy(
[
{ id: 0, value: 'a' },
{ id: 1, value: 'b' },
{ id: 2, value: 'c' },
{ id: 1, value: 'd' },
{ id: 0, value: 'e' }
],
(a, b) => a.id === b.id
) // [ { id: 2, value: 'c' } ]
// дублирующихся
const filterUniqueBy = (arr, fn) =>
arr.filter((v, i) => arr.some((x, j) => (i !== j) === fn(v, x, i, j)))
filterUniqueBy(
[
{ id: 0, value: 'a' },
{ id: 1, value: 'b' },
{ id: 2, value: 'c' },
{ id: 3, value: 'd' },
{ id: 0, value: 'e' }
],
(a, b) => a.id === b.id
)
// [ { id: 0, value: 'a' }, { id: 0, value: 'e' } ]
// --- / --- / --- / --- //
/**
* Разделение массива на части
*/
// 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 sumBy = (arr, fn) =>
arr.map(typeof fn === 'function')
? fn
: (val) => val[fn].reduce((acc, val) => acc + val, 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 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 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 diffBy = (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)))
]
}
diffBy([2.1, 1.2], [2.3, 3.4], Math.floor) // [1.2, 3.4]
diffBy(
[{ 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 getIterableSize = (val) =>
Array.isArray(val)
? val.length
: val && typeof val === 'object'
? val.size || val.length || Object.keys(val).length
: typeof val === 'string'
? new Blob([val]).size
: 0
getIterableSize([1, 2, 3]) // 3
getIterableSize('size') // 4
getIterableSize({ one: 1, two: 2, three: 3 }) // 3
// --- / --- / --- / --- //
/**
* Извлечение значений по селекторам
*/
const getObjVals = (from, ...selectors) =>
[...selectors].map((s) =>
s
.replace(/\[([^\[\]]*)\]/g, '.$1.')
.split('.')
.filter((t) => t !== '')
.reduce((prev, cur) => prev && prev[cur], from)
)
const obj = {
selector: { to: { val: 'val to select' } },
target: [1, 2, { a: 'test' }]
}
getObjVals(obj, 'selector.to.val', 'target[0]', 'target[2].a') // ["val 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 compactObj = (v) => {
const d = Array.isArray(v) ? v.filter(Boolean) : v
return Object.keys(d).reduce(
(a, k) => {
const v = d[k]
if (Boolean(v)) a[k] = typeof v === 'object' ? compactObject(v) : v
return a
},
Array.isArray(v) ? [] : {}
)
}
const obj = {
a: null,
b: false,
c: true,
d: 0,
e: 1,
f: '',
g: 'a',
h: [null, false, '', true, 1, 'a'],
i: { j: 0, k: false, l: 'a' }
}
compactObj(obj)
// { c: true, e: 1, g: 'a', h: [ true, 1, 'a' ], i: { l: 'a' } }
// --- / --- / --- / --- //
/**
* Упаковка объекта
*/
const flatObj = (obj, prefix = '') =>
Object.keys(obj).reduce((a, k) => {
const pre = prefix.length ? `${prefix}.` : ''
if (
typeof obj[k] === 'object' &&
obj[k] !== null &&
Object.keys(obj[k]).length > 0
)
Object.assign(a, flatObj(obj[k], pre + k))
else a[pre + k] = obj[k]
return a
}, {})
flatObj({ a: { b: { c: 1 } }, d: 2 }) // { a.b.c: 1, d: 2 }
// --- / --- / --- / --- //
/**
* Распаковка объекта
*/
const unflatObj = (obj) =>
Object.keys(obj).reduce((acc, key) => {
key
.split('.')
.reduce(
(a, e, i, keys) =>
a[e] ||
(a[e] = isNaN(Number(keys[i + 1]))
? keys.length - 1 === i
? obj[key]
: {}
: []),
acc
)
return acc
}, {})
unflatObj({ 'a.b.c': 1, d: 2 }) // { a: { b: { c: 1 } }, d: 2 }
unflatObj({ 'a.b': 1, 'a.c': 2, d: 3 }) // { a: { b: 1, c: 2 }, d: 3 }
// --- / --- / --- / --- //
/**
* Удаление свойств по условию - массиву ключей
*/
const omit = (obj, arr) =>
Object.keys(obj)
.filter((v) => !arr.includes(v))
.reduce((a, k) => ((a[k] = obj[k]), a), {})
omit({ a: 1, b: '2', c: 3 }, ['b']) // { a: 1, c: 3 }
// --- / --- / --- / --- //
/**
* Извлечение свойств по условию - массиву ключей
*/
const pick = (obj, arr) =>
arr.reduce((a, c) => (c in obj && (a[c] = obj[c]), a), {})
pick({ 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, v) => ((a[v] = obj[v]), a), {})
pickBy({ a: 1, b: '2', c: 3 }, (x) => typeof x === 'number')
// { b: '2' }
// --- / --- / --- / --- //
/**
* Удаление свойств по условию - функции
*/
const omitBy = (obj, fn) =>
Object.keys(obj)
.filter((key) => fn(obj[key], key))
.reduce((a, v) => ((a[v] = obj[v]), a), {})
omitBy({ a: 1, b: '2', c: 3 }, (x) => typeof x === 'number')
// { a: 1, c: 3 }
// --- / --- / --- / --- //
/**
* Группировка свойств по условию - функции
* ({ значение: [ свойства ] })
*/
const invertKeyVal = (obj, fn) =>
Object.keys(obj).reduce((acc, key) => {
const val = fn ? fn(obj[key]) : obj[key]
acc[val] = acc[val] || []
acc[val].push(key)
return acc
}, {})
invertKeyVal({ a: 1, b: 2, c: 1 }) // { 1: [ 'a', 'c' ], 2: [ 'b' ] }
invertKeyVal({ a: 1, b: 2, c: 1 }, (v) => '#' + v)
// { #1: [ 'a', 'c' ], #2: [ 'b' ] }
// --- / --- / --- / --- //
/**
* Извлечение глубоко вложенного свойства
*/
const getDeepKey = (obj, key) =>
key in obj
? obj[key]
: Object.values(obj).reduce((acc, val) => {
if (acc !== undefined) return acc
if (typeof val === 'object') return getDeepKey(val, key)
}, undefined)
const obj = {
foo: {
bar: {
baz: 'qux'
}
}
}
getDeepKey(obj, 'baz') // qux
getDeepKey(obj, 'qux') // undefined
// --- / --- / --- / --- //
/**
* Извлечение ключей по значению
*/
const findKeys = (obj, val) =>
Object.keys(obj).filter((key) => obj[key] === val)
const ages = {
John: 20,
Jane: 22,
Alice: 20
}
findKeys(ages, 20) // ['John', 'Alice']
// --- / --- / --- / --- //
/**
* Извлечение свойства по условию - функции
*/
const findKey = (obj, fn) =>
Object.keys(obj).find((key) => fn(obj[key], key, obj))
findKey(
{
john: { age: 23, active: true },
jane: { age: 24, active: false },
alice: { age: 25, active: true }
},
(x) => x['active']
)
// john
// --- / --- / --- / --- //
/**
* Объединение объектов
*/
const mergeObj = (...objs) =>
[...objs].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 objToQueryStr = (params) =>
params
? Object.entries(params).reduce((str, [k, v]) => {
const s = str.length === 0 ? '?' : '&'
str += typeof v === 'string' ? `${s}${k}=${v}` : ''
return str
}, '')
: ''
objToQueryStr({ page: '1', size: '2kg', key: undefined }) // ?page=1&size=2kg
// --- / --- / --- / --- //
/**
* Хеширование объекта
*/
const toHash = (obj, key) =>
Array.prototype.reduce.call(
obj,
(acc, v, i) => ((acc[!key ? i : v[key]] = v), acc),
{}
)
const users = [
{ id: '1', name: 'John' },
{ id: '2', name: 'Jane' },
{ id: '3', name: 'Alice' },
{ id: '4', name: 'Bob' }
]
const managers = [{ manager: 1, employees: [2, 3, 4] }]
managers.forEach(
(m) =>
(m.employees = m.employees.map(function (id) {
return this[id]
}, toHash(users, 'id')))
)
managers
// [ {manager: 1, employees: [ {id: 2, name: 'Jane'}, {id: 3, name: 'Alice'}, {id: 4, name: 'Bob'} ] } ]
// --- / --- / --- / --- //
/**
* "Привязка" методов к объекту (определение контекста методов)
*/
const bindAll = (obj, ...fns) =>
fns.forEach((fn) => ((f = obj[fn]), (obj[fn] = () => f.apply(obj))))
const view = {
label: '!',
click: function () {
console.log('clicked' + this.label)
}
}
bindAll(view, 'click')
window.addEventListener('click', view.click)
// --- / --- / --- / --- //
/**
* Глубокая "заморозка" объекта (объект становится неизменяемым - иммутабельным)
*/
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 matches = (obj, source) =>
Object.keys(source).every(
(key) => obj.hasOwnProperty(key) && obj[key] === source[key]
)
matches(
{
age: 25,
hair: 'long',
beard: true
},
{
hair: 'long',
beard: true
}
) // true
matches(
{
hair: 'long',
beard: true
},
{
age: 25,
hair: 'long',
beard: true
}
) // false
// --- / --- / --- / --- //
/**
* Поиск совпадения по условию - функции
*/
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] = new ProxyObj({})
return target[prop]
}
})
const obj = new deepProp({})
obj.x.y.z = 'foo'
console.log(obj.x.y.z) // foo
// --- / --- / --- / --- //
/**
* Создание итерируемого объекта
*/
// 1
const makeIterable = (obj) => {
Object.defineProperty(obj, 'length', {
value: Object.keys(obj).length
})
obj[Symbol.iterator] = (i = 0, values = Object.values(obj)) => ({
next: () =>
i < obj.length ? { done: false, value: values[i++] } : { done: true }
})
return obj
}
const iterableObj = makeIterable({
name: 'John',
age: 24
})
for (const v of iterableObj) console.log(v) // John 24
console.log(...iterableObj) // John 24
// 2
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 makeReadOnlyObj = (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 = makeReadOnlyObj({ 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 log = console.log
const printNums = async () => {
log(1)
await sleep(1000)
log(2)
await sleep(1000)
log(3)
}
printNums() // 1 2 3 с задержкой в 1 секунду между каждым `log`
// --- / --- / --- / --- //
/**
* Измерение времени выполнения функции
*/
const timeTaken = (fn) => {
console.time('t')
const r = fn()
console.timeEnd('t')
return r
}
// --- / --- / --- / --- //
/**
* Выполнение функции только при удовлетворении условия
*/
const makeWhen = (make, when) => (x) => (make(x) ? when(x) : x)
const doubleEvenNums = when(
(n) => !(n & 1),
(n) => n * 2
)
doubleEvenNums(2) // 4
// --- / --- / --- / --- //
/**
* Определение типа переданного значения
*/
// 1
const getType = (v) =>
v === 'undefined' ? 'undefined' : v === null ? 'null' : v.constructor.name
getType([]) // Array
getType(() => {}) // Function
// 2
const _getType = (value) => Object.prototype.toString.call(value).slice(8, -1)
_getType({}) // Object
_getType(function () {}) // Function
// --- / --- / --- / --- //
/**
* Асинхронное выполнение функции с помощью веб-воркера (web worker)
*/
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 = (er) => {
rej(er), worker.terminate()
}
})
}
const longRunFn = () => {
let res = 0
for (let i = 0; i < 1000; i++)
for (let j = 0; j < 700; j++)
for (let k = 0; k < 300; k++) res = res + i + j + k
return res
}
runAsync(longRunFn).then(log) // 209685000000
// --- / --- / --- / --- //
/**
* Последовательное выполнение промисов
*/
const runPromiseSeq = (ps) =>
ps.reduce((p, next) => p.then(next), Promise.resolve())
const delay = (d) => new Promise((r) => setTimeout(r, d))
runPromiseSeq([() => delay(1000), () => delay(2000)])
// --- / --- / --- / --- //
/**
* Определение самой производительной функции
*/
const mostPerform = (fns, iter = 10000) => {
const times = fns.map((fn) => {
const before = performance.now()
for (let i = 0; i < iter; i++) fn()
return performance.now() - before
})
return times.indexOf(Math.min(...times))
}
// --- / --- / --- / --- //
/**
* Однократное выполнение функции
*/
const once = (fn) => {
let called = false
return function (...args) {
if (called) return
called = true
return fn.apply(this, args)
}
}
// --- / --- / --- / --- //
/**
* Выполнение функции указанное количество раз
*/
const times = (n, fn, ctx = undefined) => {
let i = 0
while (fn.call(ctx, i) !== false && ++i < n) {}
}
let str = ''
times(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 curr = 0
const last = fns[fns.length - 1]
const next = () => {
const fn = fns[curr++]
fn === last ? fn() : fn(next)
}
next()
}
chainAsync([
(next) => {
log('0 seconds')
setTimeout(next, 1000)
},
(next) => {
log('1 second')
setTimeout(next, 1000)
},
() => {
log('2 seconds')
}
])
// --- / --- / --- / --- //
/**
* Отложенное выполнение функции
*/
const defer = (fn, ...args) => setTimeout(fn, 1, ...args)
// --- / --- / --- / --- //
/**
* Привязка функции к объекту (определение контекста метода)
*/
const bindKey = (ctx, fn, ...boundArgs) => (...args) =>
ctx[fn].apply(ctx, [...boundArgs, ...args])
const john = {
user: 'John',
greet: function (phrase, punct) {
return `${phrase} ${this.user}${punct}`
}
}
const johnBound = bindKey(john, 'greet')
johnBound('Hi,', '!') // Hi, John!
// --- / --- / --- / --- //
/**
* Синхронное (последовательное) выполнение функций
* (возвращается массив подмассивов результатов выполнения функций)
*/
const juxt = (...fns) => (...args) => [...fns].map((fn) => [...args.map(fn)])
juxt(
(x) => x + 1,
(x) => x - 1,
(x) => x * 10
)(1, 2, 3)
// [ [2, 3, 4], [0, 1, 2], [10, 20, 30]
juxt(
(s) => s.length,
(s) => s.split(' ').join('-')
)('JavaScript is cool')
// [ [21], ['JavaScript-is-cool'] ]
// --- / --- / --- / --- //
/**
* Объединение функций
*/
const over = (...fns) => (...args) => fns.map((fn) => fn.apply(null, args))
const minMax = over(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('Привет!')
})
// --- / --- / --- / --- //
/**
* Мемоизация (запоминание результатов выполнения функции, memoization)
* https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F
*/
// 1
const memo = (fn) => {
const cache = new Map()
return (n) => {
if (cache.has(n)) {
console.log('From cache')
return cache.get(n)
} else {
console.log('Calculated')
const res = fn(n)
cache.set(n, res)
return res
}
}
}
const add = (x) => x + 10
const memoAdd = memo(add)
memoAdd(10) // Caclulated 20
memoAdd(10) // From cache 20
// 2
const memo = (fn) =>
new Proxy(fn, {
cache: new Map(),
apply(target, arg, args) {
const key = args.toString()
if (!this.cache.has(key)) this.cache.set(key, target.apply(arg, args))
return this.cache.get(key)
}
})
// 3
const _memo = (fn, cache = new Map()) => (val) =>
cache.get(val) || (cache.set(val, fn(val)) && cache.get(val))
const fact = (n) => (n < 1 ? 1 : n * fact(n - 1))
const memoFact = _memo(fact)
console.time('t1')
console.log(memoFact(150))
console.timeEnd('t1') // 0.15
console.time('t2')
console.log(memoFact(150))
console.timeEnd('t2') // 0.05
// --- / --- / --- / --- //
/**
* Каррирование (карринг, уменьшение "арности" функции, currying)
* https://ru.wikipedia.org/wiki/%D0%9A%D0%B0%D1%80%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5
*/
// 1
// es5
function curryRegSum(a) {
return function (b) {
return function (c) {
return a + b + c
}
}
}
// es6
const curryArrowSum = (a) => (b) => (c) => a + b + c
curryArrowSum(1) // b => c => 1 + b + c
curryArrowSum(1)(2) // c => 3 + c
curryArrowSum(1)(2)(3) // 6
// 2
const curryCalcEval = (...x) => (op) =>
x.reduce((acc, cur) => (acc = eval(`acc ${op} cur`)))
curryCalcEval(1, 2, 3)('+') // 6
// создание "каррированной" функции
// 1
const randomCurry = (fn) =>
function curry(...args) {
return fn.length <= args.length
? fn.apply(this, args)
: (...args2) => curry.apply(this, args.concat(args2))
}
const sum = randomCurry((a, b, c, d) => a + b + c + d)
sum(1)(2)(3)(4) // 10
sum(1, 2)(3, 4) // 10
// 2
const curry = (fn, arity = fn.length, ...args) =>
arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args)
curry(Math.pow)(2)(10) // 1024
curry(Math.min, 3)(10)(50)(2) // 2
// --- / --- / --- / --- //
/**
* Частичное применение функции (практические применение каррирования)
*/
const partial = (fn, ...a) => (...b) => fn(...a, ...b)
const greet = (phrase, name) => `${phrase}, ${name}!`
const hello = partial(greet, 'Привет')
hello('Иван') // Привет, Иван!
// еще один пример
const mult = (...x) => x.reduce((a, b) => (a *= b))
const partMult = partial(mult, 2) // 2
partMult(3) // 2 * 3 = 6
// --- / --- / --- / --- //
/**
* "Декаррирование" (преобразование каррированной функции в обычную)
*/
const uncurry = (fn, n = 1) => (...args) => {
const next = (acc) => (args) => args.reduce((x, y) => x(y), acc)
if (n > args.length) throw new RangeError('Arguments too few!')
return next(fn)(args.slice(0, n))
}
const curAdd = (x) => (y) => (z) => x + y + z
const uncurAdd = uncurry(add, 3)
uncurAdd(1, 2, 3) // 6
// --- / --- / --- / --- //
/**
* Debounce
*/
// 1
const debounce = (f, t) =>
function (args) {
let prevCall = this.lastCall
this.lastCall = Date.now()
if (prevCall && this.lastCall - prevCall <= t) {
clearTimeout(this.timer)
}
this.timer = setTimeout(() => f(args), t)
}
const log = (args) => console.log(`Args: ${args}`)
const debouncedLog = debounce(log, 2000)
const arr = [1, 2, 3]
debouncedLog(arr)
debouncedLog(arr)
debouncedLog(arr)
// Args: 1, 2, 3 - один раз, через 2 секунды
// 2
const _debounce = (fn, ms = 0) => {
let id
return function (...args) {
clearTimeout(id)
id = setTimeout(() => fn.apply(this, args), ms)
}
}
// --- / --- / --- / --- //
/**
* Throttle
*/
const throttle = (f, t) =>
function (args) {
let prevCall = this.lastCall
this.lastCall = Date.now()
if (prevCall === undefined || this.lastCall - prevCall > t) {
f(args)
}
}
const log = (args) => console.log(`Args: ${args}`)
const throttledLog = throttle(log, 2000)
const arr = [1, 2, 3]
throttledLog(arr)
throttledLog(arr)
throttledLog(arr)
// Args: 1, 2, 3 - сразу, один раз
/**
* Копирование строки в буфер обмена
*/
// 1
const copyToClip = (str) => {
const $ = document.createElement('textarea')
$.value = str
document.body.append($)
$.select()
document.execCommand('copy')
$.remove()
}
// 2
const copy = async (text) => await navigator.clipboard.writeText(text)
/**
* Вставка строки из буфера обмена
*/
const paste = async (el) => {
const text = await navigator.clipboard.readText()
el.textContent = text
}
// --- / --- / --- / --- //
/**
* Определение элемента, находящегося в фокусе
*/
const isFocused = (el) => el === document.activeElement
// --- / --- / --- / --- //
/**
* Находится ли элемент в области просмотра
* (полностью или частично)?
*/
const isVisible = (el, part = false) => {
const { top, left, bottom, right } = el.getBoundingClientRect()
const { innerHeight, innerWidth } = window
return part
? ((top > 0 && top < innerHeight) ||
(bottom > 0 && bottom < innerHeight)) &&
((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
: top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth
}
// --- / --- / --- / --- //
/**
* Переключение в полноэкранный режим
*/
const fullscreen = (mode = true, el = 'body') =>
mode
? document.querySelector(el).requestFullscreen()
: document.exitFullscreen()
// HTML
// <button>fullscreen</button>
document.querySelector('button').onclick = () => {
fullscreen()
}
// --- / --- / --- / --- //
/**
* Определение величины прокрутки
*/
const getScrollPosition = (el = window) => ({
x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
})
// --- / --- / --- / --- //
/**
* Определение предпочтения пользователем темной цветовой темы (схемы)
*/
const prefersDarkColorScheme = () =>
window &&
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches
// --- / --- / --- / --- //
/**
* Получение/установка стилей элемента
*/
const getStyle = (el, prop) => getComputedStyle(el)[prop]
const setStyle = (el, prop, val) => (el.style[prop] = val)
// --- / --- / --- / --- //
/**
* Добавление стилей в виде объекта
*/
const addStyles = (el, obj) => {
Object.assign(el.style, obj)
}
// --- / --- / --- / --- //
/**
* Создание элемента с помощью шаблонных литералов
*/
// 1
const createElFromStr = (str) => {
const el = document.createElement('div')
el.innerHTML = str
const child = el.fisrtElementChild
el.remove()
return child
}
const el = createEl(
`<div class="container">
<p>Hello!</p>
</div>`
)
console.log(el.className) // container
// 2
const createElFromStr = (str) => {
const parser = new DOMParser()
const {
body: { children }
} = parser.parseFromString(str, 'text/html')
return children[0]
}
// 3
const createElFromStr = (str) => {
const range = new Range()
const fragment = range.createContextualFragment(str)
return fragment
}
// или в одну строку
const createElFromStr = (str) => new Range().createContextualFragment(str)
// --- / --- / --- / --- //
/**
* Определение устройства пользователя
*/
const detectDeviceType = () =>
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
)
? 'Mobile'
: 'Desktop'
detectDeviceType() // 'Mobile' or 'Desktop'
// --- / --- / --- / --- //
/**
* Рендеринг элемента
*/
const renderEl = ({ type, props = {} }, container) => {
const isTextEl = !type
const el = isTextEl
? document.createTextNode('')
: document.createElement(type)
const isListener = (p) => p.startsWith('on')
const isAttr = (p) => !isListener(p) && p !== 'children'
Object.keys(props).forEach((p) => {
if (isAttr(p)) el[p] = props[p]
if (!isTextEl && isListener(p))
el.addEventListener(p.toLowerCase().slice(2), props[p])
})
if (!isTextEl && props.children && props.children.length)
props.children.forEach((child) => renderEl(child, el))
container.append(el)
}
const el = {
type: 'button',
props: {
type: 'button',
className: 'btn',
onClick: () => alert('clicked'),
children: [{ props: { nodeValue: 'click me' } }]
}
}
renderEl(el, document.body)
// --- / --- / --- / --- //
/**
* "Сериализация" формы
*/
const serializeForm = (form) =>
Array.from(new FormData(form), (field) =>
field.map(encodeURIComponent).join('=')
).join('&')
// --- / --- / --- / --- //
/**
* Преобразование формы в объект
*/
const formToObj = (form) =>
Array.from(new FormData(form)).reduce(
(acc, [key, val]) => ({
...acc,
[key]: val
}),
{}
)
// --- / --- / --- / --- //
/**
* Преобразование массива в список элементов `li`
*/
const arrToList = (arr, selector) =>
(document.querySelector(selector).innerHTML += arr
.map((i) => `<li>${i}</li>`)
.join(''))
// HTML
// <ul id="list"></ul>
arrToList(['foo', 'bar'], '#list')
// --- / --- / --- / --- //
/**
* Наблюдение за всеми изменениями, происходящими в документе
*/
const observeMutations = (el, cb, opts) => {
const O = new MutationObserver((ms) => ms.forEach((m) => cb(m)))
O.observe(
el,
Object.assign(
{
childList: true,
attributes: true,
attributeOldValue: true,
characterData: true,
characterDataOldValue: true,
subtree: true
},
opts
)
)
return O
}
observeMutations(document, log)
// --- / --- / --- / --- //
/**
* Добавление обработчика событий
*/
const on = (el, ev, fn, opts = {}) => {
const delegator = (e) => e.target.matches(opts.target) && fn.call(e.target, e)
el.addEventListener(ev, opts.target ? delegator : fn, opts.options || false)
if (opts.target) return delegator
}
const fn = () => console.log('!')
on(document, 'click', fn) // при клике по `body` выводится '!'
// HTML
// <p>click me</p>
on(document, 'click', fn, { target: 'p' })
// при клике по `p` выводится '!'
on(document, 'click', fn, { options: true })
// capturing вместо bubbling
// --- / --- / --- / --- //
/**
* Удаление обработчика событий
*/
const off = (el, ev, fn, opt = false) => {
el.removeEventLisntener(ev, fn, opt)
}
const fn = () => console.log('!')
window.addEventListener('click', fn)
off(window, 'click', fn)
// --- / --- / --- / --- //
/**
* Вызов пользовательского ("кастомного") события
*/
const trigger = (el, type, opts) =>
el.dispatchEvent(new CustomEvent(type, { opts }))
const getOne = (selector, el = document) => el.querySelector(selector)
const par = getOne('#username')
const btn = getOne('button')
const fn = ({ target, detail: { username } }) => {
target.textContent = username
}
on(el, 'click', fn)
// HTML
// <p id="username"></p>
// <button>trigger event</button>
btn.onclick = () => {
trigger(el, 'click', { username: 'John' })
}
// --- / --- / --- / --- //
/**
* Адреса изображений (с дубликатами или без)
*/
const getImgs = (el, dup = false) => {
const imgs = [...el.querySelectorAll('img')].map((i) => i.getAttribute('src'))
return dup ? imgs : [...new Set(imgs)]
}
// --- / --- / --- / --- //
/**
* Один/все элементы, соответствующие указанному селектору
*/
const getOne = (selector, el = document) => el.querySelector(selector)
const getAll = (selector, el = document) => [...el.querySelectorAll(selector)]
const getEl = (selector, all = false, el = document) => all ? [...el.querySelectorAll(selector)] : el.querySelector(selector)
// --- / --- / --- / --- //
/**
* Cкрытие/отображение указанных элементов
*/
const getAll = (selector, el = document) => [...el.querySelectorAll(selector)]
const hide = (val) => {
const arr = typeof val === 'string' ? getAll(val) : [...val]
arr.forEach((i) => {
i.style.display = 'none'
})
}
const show = (val) => {
const arr = typeof val === 'string' ? getAll(val) : [...val]
arr.forEach((i) => {
i.style.display = ''
})
}
// --- / --- / --- / --- //
/**
* Определение завершения прокрутки
*/
const onScrollStop = (cb) => {
let isScroll
window.addEventListener(
'scroll',
() => {
clearTimeout(isScroll)
isScroll = setTimeout(() => {
cb()
}, 300)
},
false
)
}
onScrollStop(() => {
console.log('scrolling stopped')
})
// --- / --- / --- / --- //
/**
* Регистрация клика за пределами и внутри указанного элемента
*/
const onClickOutside = (el, cb) => {
document.addEventListener('click', ({ target }) => {
if (!el.contains(target)) cb()
})
}
const onClickInside = (el, cb) => {
document.addEventListener('click', ({ target }) => {
if (el.contains(target)) cb()
})
}
// --- / --- / --- / --- //
/**
* Запуск/остановка покадровой отрисовки страницы
*/
const recordAnimFrames = (cb, auto = true) => {
let isRun = false,
raf
const stop = () => {
if (!isRun) return
isRun = false
cancelAnimationFrame(raf)
}
const start = () => {
if (isRun) return
isRun = true
run()
}
const run = () => {
raf = requestAnimationFrame(() => {
cb()
if (isRun) run()
})
}
if (auto) start()
return { start, stop }
}
// --- / --- / --- / --- //
/**
* Определение всех предков элемента
*/
const getAncestors = (el) => {
let ans = []
while (el) {
ans.unshift(el)
el = el.parentNode
}
return ans
}
// --- / --- / --- / --- //
/**
* Определение наличия, добавление/удаление класса
*/
const hasClass = (el, str, part = false) =>
!part ? el.classList.contains(str) : el.className.includes(str)
const addClass = (el, str, part = false) =>
!part ? (el.className = str) : el.classList.add(str)
const removeClass = (el, str, part = false) =>
!part ? (el.className = '') : el.classList.remove(str)
// --- / --- / --- / --- //
/**
* Плавная прокрутка в начало страницы
*/
const scrollToTop = () => {
const c = document.documentElement.scrollTop || document.body.scrollTop
if (c > 0) {
window.requestAnimationFrame(scrollToTop)
window.scrollTo(0, c - c / 8)
}
}
// HTML
// <button>top</button>
document.querySelector('button').onclick = () => {
scrollToTop()
}
// --- / --- / --- / --- //
/**
* Плавная прокрутка к указанному элементу
*/
const getOne = (selector, el = document) => el.querySelector(selector)
const scroll = (selector) =>
getOne(selector).scrollIntoView({
behavior: 'smooth'
})
// --- / --- / --- / --- //
/**
* Удаление, замена ("обеззараживание") и восстановление HTML
*/
const removeHtml = (str) => str.replace(/<[^>]*>/g, '')
const escapeHtml = (str) =>
str.replace(
/[&<>'"]/g,
(tag) =>
({
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"'
}[tag] || tag)
)
escapeHtml('<a href="#">React & TypeScript</a>')
// '<a href="#">React & TypeScript</a>'
const unescapeHtml = (str) =>
str.replace(
/&|<|>|'|"/g,
(tag) =>
({
'&': '&',
'<': '<',
'>': '>',
''': "'",
'"': '"'
}[tag] || tag)
)
unescapeHtml('<a href="#">React & TypeScript</a>')
// '<a href="#">React & TypeScript</a>'
/**
* Работа с URL
*/
// получение базового адреса
const getBaseURL = (url) => url.replace(/[?#].*$/, '')
getBaseURL('http://example.com/page?name=John&surname=Smith') // http://example.com
// является ли URL абсолютным?
const isAbsoluteURL = (str) => /^[a-z][a-z0-9+.-]*:/.test(str)
isAbsoluteURL('https://google.com') // true
isAbsoluteURL('ftp://www.myserver.net') // true
isAbsoluteURL('/foo/bar') // false
// перенаправление с http на https
const httpsRedirect = () => {
if (location.protocol !== 'https:')
location.replace('https://' + location.href.split('//')[1])
}
// извлечение параметров URL
const getURLParams = (url) =>
(url.match(/([^?=&]+)(=([^&]*))/g) || []).reduce(
(a, v) => (
(a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a
),
{}
)
getURLParams('http://url.com/page?name=John&surname=Smith') // { name: 'John', surname: 'Smith' }
// преобразование строки запроса в объект
const queryStrToObj = (url) =>
[...new URLSearchParams(url.split('?')[1])].reduce(
(a, [k, v]) => ((a[k] = v), a),
{}
)
queryStrToObj('https://google.com?page=1&count=10')
// { page: '1', count: '10' }
// генерация URL
const joinURL = (...args) =>
args
.join('/')
.replace(/[\/]+/g, '/')
.replace(/^(.+):\//, '$1://')
.replace(/^file:/, 'file:/')
.replace(/\/(\?|&|#[^!])/g, '$1')
.replace(/\?/g, '&')
.replace('&', '?')
joinURL('http://www.google.com', 'a', '/b/cd', '?foo=123', '?bar=baz')
// http://www.google.com/a/b/cd?foo=123&bar=baz
// --- / --- / --- / --- //
/**
* GET/POST-запросы
*/
// GET
// XMLHttpRequest
const httpGet = (url, cb, err = console.error) => {
const req = new XMLHttpRequest()
req.open('GET', url, true)
req.onload = () => cb(req.responseText)
req.onerror = () => err(req)
req.send()
}
// async/await
const httpGet = async (url, cb, err = console.error) => {
try {
const response = await fetch(url)
const result = await response.json()
cb(result)
} catch (error) {
err(error)
}
}
// POST
// XMLHttpRequest
const httpPost = (url, data, cb, err = console.error) => {
const req = new XMLHttpRequest()
req.open('POST', url, true)
req.setRequestHeader('Content-Type', 'application/json')
req.onload = () => cb(req.responseText)
req.onerror = () => err(req)
req.send(data)
}
// async/await
const httpPost = async (url, data, cb, err = console.error) => {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
const result = await response.json()
cb(result)
} catch (error) {
err(error)
}
}
// --- / --- / --- / --- //
/**
* Работа с цветом
*/
// 1
const getRandomHexColor = () =>
`#${((Math.random() * 0xffffff) << 0).toString(16)}`
// 2
const getRandomRGBAColor = (opacity) => {
const random = () => ~~(Math.random() * 255)
return `rgba(${random()}, ${random()}, ${random()}, ${opacity})`
}
// 3
const RGBToHex = (r, g, b) =>
((r << 16) + (g << 8) + b).toString(16).padStart(6, '0')
RGBToHex(255, 165, 1) // 'ffa501'
// --- / --- / --- / --- //
/**
* Генерация уникальных значений
*/
// 1
const getRandomAlphaNumeric = (length) => {
let s = ''
Array.from({ length }).some(() => {
s += Math.random().toString(36).slice(2)
return s.length >= length
})
return s.slice(0, length)
}
getRandomAlphaNumeric(5) // bf8lf
// 2
// вспомогательная функция, обеспечивающая
// уникальность генерируемых значений
function outerMemo(fn, cache = new Set()) {
return function innerMemo(res = fn()) {
if (cache.has(res)) {
return innerMemo()
} else {
cache.add(res)
return res
}
}
}
const getUniqId = outerMemo(() =>
(~~(Math.random() * 1e6)).toString(16).slice(0, 4).padStart(5, 'x')
)
getUniqId() // xc1b9
// 3
const genUUID = () =>
([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
(
c ^
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
).toString(16)
)
genUUID() // 7982fcfe-5721-4632-bede-6000885be57d
// --- / --- / --- / --- //
/**
* Работа с датами
*/
// 1
const getDaysBetween = (start, end) =>
Array.from({ length: (end - start) / (1000 * 3600 * 24) }).reduce((c) => {
if (start.getDay() % 6 !== 0) c++
start = new Date(start.setDate(start.getDate() + 1))
return c
}, 0)
getDaysBetween(new Date('2021-02-06'), new Date('2021-02-08')) // 2
// 2
const changeDay = (n) => {
let d = new Date()
d.setDate(d.getDate() + n)
return d.toLocaleDateString()
}
changeDay(-1) // 18.02.2021
// --- / --- / --- / --- //
/**
* Разбор куки
*/
const parseCookie = (str) =>
str
.split(';')
.map((v) => v.split('='))
.reduce((acc, cur) => {
acc[decodeURIComponent(cur[0].trim())] = decodeURIComponent(cur[1].trim())
return acc
}, {})
parseCookie('foo=bar; equation=E%3Dmc%5E2')
// { foo: "bar", equation: "E=mc^2" }
// --- / --- / --- / --- //
/**
* Счетчики
*/
// 1
const createCounter = (n = 0) => ({
get: () => n,
plus: () => ++n,
minus: () => --n,
reset: () => (n = 0)
})
const log = console.log
const counter = createCounter(5)
log(counter.get()) // 5
counter.plus()
counter.plus()
log(counter.get()) // 7
counter.reset()
log(counter.get()) // 0
// 2
const counter = (selector, start, end, step = 1, duration = 2000) => {
const el = document.querySelector(selector)
let current = start,
_step = (end - start) * step < 0 ? -step : step,
timer = setInterval(() => {
current += _step
el.textContent = current
if (current >= end) el.textContent = end
if (current >= end) clearInterval(timer)
}, Math.abs(~~(duration / (end - start))))
}
// HTML
// <p id="counter"></p>
counter('#counter', 0, 100, 5, 10000)
// --- / --- / --- / --- //
/**
* Генераторы
*/
// 1
const range = function* (start, end, step = 1) {
let i = start
while (i < end) {
yield i
i += step
}
}
// 2
const _range = (end, start = 0, step = 1) => {
function* generate() {
let x = start - step
while (x <= end - step) yield (x += step)
}
return {
[Symbol.iterator]: generate
}
}
// --- / --- / --- / --- //
/**
* Переключатель
*/
const toggle = (...args) => {
let state = -1
const len = args.length
return () => {
state = (state + 1) % len
return args[state]
}
}
const onOff = toggle('on', 'off')
console.log(onOff()) // on
console.log(onOff()) // off
// --- / --- / --- / --- //
/**
* Публикация/подписка
*/
const pubSub = (subscribers = []) => ({
publish: ({ message, sender }) => {
subscribers.forEach(({ name }) => {
console.log(`${name} get ${message} from ${sender}`)
})
},
subscribe: (subscriber) => {
subscribers.push(subscriber)
}
})
const pubSubObj = pubSub()
pubSubObj.subscribe({ name: 'John' })
pubSubObj.subscribe({ name: 'Jane' })
pubSubObj.publish({ message: 'Hello', sender: 'Bob' })
// John get Hello from Bob
// Jane get Hello from Bob
// --- / --- / --- / --- //
/**
* "Шина" событий
*/
const createEventHub = () => ({
hub: Object.create(null),
emit(e, d) {
;(this.hub[e] || []).forEach((h) => h(d))
},
on(e, h) {
if (!this.hub[e]) this.hub[e] = []
this.hub[e].push(h)
},
off(e, h) {
const i = (this.hub[e] || []).findIndex((handler) => handler === h)
if (i > -1) this.hub[e].splice(i, 1)
if (this.hub[e].length === 0) delete this.hub[e]
}
})
// --- / --- / --- / --- //
/**
* Fizz buzz
* https://ru.wikipedia.org/wiki/Fizz_buzz
*/
// 1
const fizzBuzz = (n) => {
const arr = []
do {
if (n % 15 === 0) arr.push('Fizz Buzz')
else if (n % 3 === 0) arr.push('Fizz')
else if (n % 5 === 0) arr.push('Buzz')
else arr.push(n)
} while (n-- > 1)
return arr
}
fizzBuzz(15)
// ["Fizz Buzz", 14, 13, "Fizz", 11, "Buzz", "Fizz", 8, 7, "Fizz", "Buzz", 4, "Fizz", 2, 1]
// 2
const _fizzBuzz = (n) => {
const arr = []
for (let i = 1; i <= n; i++) {
let a = i % 3 === 0,
b = i % 5 === 0
arr.push(a ? (b ? 'FizzBuzz' : 'Fizz') : b ? 'Buzz' : i)
}
return arr
}
_fizzBuzz(15)
// [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz"]
// --- / --- / --- / --- //
/**
* Лестница (stairs)
*/
// 1
const steps = (n) => {
let stairs = ''
for (let r = 0; r < n; r++) {
let stair = ''
for (let c = 0; c < n; c++) {
stair += c <= r ? '#' : ' '
}
stairs += stair + '\n'
}
return stairs
}
steps(5)
/*
#
##
###
####
#####
*/
// 2
const _steps = (n, r = 0, stair = '', stairs = '') => {
if (r === n) return stairs
if (stair.length === n) return _steps(n, r + 1, '', stairs + stair + '\n')
return _steps(n, r, stair + (stair.length <= r ? '#' : ' '), stairs)
}
// --- / --- / --- / --- //
/**
* Пирамида (pyramid)
*/
// 1
const pyramid = (n) => {
let levels = ''
const mid = ~~((2 * n - 1) / 2)
for (let r = 0; r < n; r++) {
let level = ''
for (let c = 0; c < 2 * n - 1; c++) {
level += mid - r <= c && c <= mid + r ? '#' : ' '
}
levels += level + '\n'
}
return levels
}
pyramid(5)
/*
#
###
#####
#######
#########
*/
// 2
const _pyramid = (n, r = 0, level = '', levels = '') => {
if (n === r) return levels
if (2 * n - 1 === level.length)
return _pyramid(n, r + 1, '', levels + level + '\n')
const mid = ~~((2 * n - 1) / 2)
return _pyramid(
n,
r,
level + (mid - r <= level.length && level.length <= mid + r ? '#' : ' '),
levels
)
}
// --- / --- / --- / --- //
/**
* Спиральная матрица (spiral matrix)
*/
const spiral = (n) => {
let counter = 1
let startR = 0,
endR = n - 1
let startC = 0,
endC = n - 1
const matrix = []
for (let i = 0; i < n; i++) matrix.push([])
while (startC <= endC && startR <= endR) {
for (let i = startC; i <= endC; i++) {
matrix[startR][i] = counter
counter++
}
startR++
for (let i = startR; i <= endR; i++) {
matrix[i][endC] = counter
counter++
}
endC--
for (let i = endC; i >= startC; i--) {
matrix[endR][i] = counter
counter++
}
endR--
for (let i = endR; i >= startR; i--) {
matrix[i][startC] = counter
counter++
}
startC++
}
return matrix
}
spiral(3)
/*
[
[1, 2, 3],
[8, 9, 4],
[7, 6, 5]
]
*/
// --- / --- / --- / --- //
/**
* Шифр Цезаря
* https://ru.wikipedia.org/wiki/%D0%A8%D0%B8%D1%84%D1%80_%D0%A6%D0%B5%D0%B7%D0%B0%D1%80%D1%8F
*/
const caesarCipher = (str, num) => {
const arr = [...'abcdefghijklmnopqrstuvwxyz']
let newStr = ''
for (const char of str) {
const lower = char.toLowerCase()
if (!arr.includes(lower)) {
newStr += char
continue
}
let index = arr.indexOf(lower) + (num % 26)
if (index > 25) index -= 26
if (index < 0) index += 26
newStr +=
char === char.toUpperCase() ? arr[index].toUpperCase() : arr[index]
}
return newStr
}
caesarCipher('Hello World', 100) // Dahhk Sknhz
caesarCipher('Dahhk Sknhz', -100) // Hello World
/**
* Отключение кэширования
*/
const requireUncached = (module) => {
delete requireUncached.cache[requireUncached.resolve(module)]
return require(module)
}
const fs = requireUncached('fs')
// --- / --- / --- / --- //
/**
* Безопасное создание директории
*/
const fs = require('fs')
const createDir = (dir = !fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined)
createDir()
// --- / --- / --- / --- //
/**
* Декодирование base64-строки
*/
const atob = (str) => Buffer.from(str, 'base64').toString('binary')
atob('Zm9vYmFy') // foobar
// --- / --- / --- / --- //
/**
* Шифрование объекта
*/
const crypto = require('crypto')
const hashNode = (val) =>
new Promise((res) =>
setTimeout(
() => res(crypto.createHash('sha256').update(val).digest('hex')),
0
)
)
hashNode(JSON.stringify(obj)).then(log)
// --- / --- / --- / --- //
/**
* Генерация UUID
*/
const crypto = require('crypto')
const genUuid = () =>
([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
(c ^ (crypto.randomBytes(1)[0] & (15 >> (c / 4)))).toString(16)
)
genUuid()
// '79c7c136-60ee-40a2-beb2-856f1feabefc'
// --- / --- / --- / --- //
/**
* Построчное чтение файла
*/
const fs = require('fs')
const readFileLines = (filename) =>
fs.readFileSync(filename).toString('utf8').split('\n')
const fs = require('fs')
// --- / --- / --- / --- //
/**
* Преобразование объекта в JSON-файл
*/
const jsonToFile = (obj, filename) =>
fs.writeFileSync(`${filename}.json`, JSON.stringify(obj, null, 2))
// --- / --- / --- / --- //
/**
* "Кастомизация" консольного вывода
*/
const colorize = (...args) => ({
black: `\x1b[30m${args.join(' ')}`,
red: `\x1b[31m${args.join(' ')}`,
green: `\x1b[32m${args.join(' ')}`,
yellow: `\x1b[33m${args.join(' ')}`,
blue: `\x1b[34m${args.join(' ')}`,
magenta: `\x1b[35m${args.join(' ')}`,
cyan: `\x1b[36m${args.join(' ')}`,
white: `\x1b[37m${args.join(' ')}`,
bgBlack: `\x1b[40m${args.join(' ')}\x1b[0m`,
bgRed: `\x1b[41m${args.join(' ')}\x1b[0m`,
bgGreen: `\x1b[42m${args.join(' ')}\x1b[0m`,
bgYellow: `\x1b[43m${args.join(' ')}\x1b[0m`,
bgBlue: `\x1b[44m${args.join(' ')}\x1b[0m`,
bgMagenta: `\x1b[45m${args.join(' ')}\x1b[0m`,
bgCyan: `\x1b[46m${args.join(' ')}\x1b[0m`,
bgWhite: `\x1b[47m${args.join(' ')}\x1b[0m`
})
colorize('foo').red
colorize(colorize('bar').yellow, colorize('foo').green).bgMagenta