25 полезных сниппетов JavaScript для вашей коллекции
Подборка полезных фрагментов кода на JavaScript для решения распространенных задач программирования. Этот джентльменский набор сниппетов пригодится любому разработчику. Обсудить В программировании есть классические задачи, которые повторяются от проекта к проекту. Натыкаясь на них в десятый раз, вы начинаете задумываться о создании базы сниппетов, чтобы не писать один и тот же код с нуля. В этой статье мы собрали 25 таких трюков, которые вам обязательно пригодятся. JavaScript не имеет сильной системы типов, что многие считают серьезным недостатком. Поклонники строгости по достоинству оценят TypeScript, но и в самом JS есть некоторые полезные возможности. Например, провести простую проверку типа можно с помощью ключевого слова Однако его возможности ограничены. С примитивами и функциями все работает как надо, но массивы от простых объектов вы отличить не сможете – они все имеют тип Давайте соберем все возможные проверки в одну полезную функцию: isOfType.js Код функции на GitHub Часто требуется определить, является ли значение пустым. Однако само понятие пустоты разное для разных типов данных. Например, в пустой строке нет символов, а в пустом объекте – ключей. Соответственно, нужно использовать разные методы: isEmpty.js Код функции на Github Во многих языках у коллекций уже есть встроенный метод для получения последнего элемента, но в JavaScript нам приходится делать это самостоятельно. Главное не забыть об особенностях разных типов списков: lastIem.js Код функции на Github Эта функция достаточно проста, чтобы не использовать для генерирования случайных чисел отдельную библиотеку, но недостаточно проста, чтобы ее запомнить. Поэтому многие разработчики каждый раз обращаются к Google в поисках заветных строк: randomNumber.js Код функции на GitHub Если вам просто нужен уникальный идентификатор (а не что-то сложное вроде UUID), то вы вполне можете обойтись без специальных библиотек. Используйте текущее время, инкремент или символы алфавита: unique-id.js Код функции на Github В JavaScript очень не хватает функции для генерации последовательности чисел – вроде range в Python. Она была бы очень полезна для циклов for…of и других ситуаций, когда нужен определенный диапазон значений. range.js Код функции на Github Наверняка во время разработки вы активно пользуетесь консолью для тестирования и вывода данных. Для этого удобно применять метод JSON.stringify. Его третий параметр определяет количество пробелов для форматирования отступов. Кроме того есть второй параметр, который может быть полезен для обработки непримитивных типов данных – функций, символов и т.п., которые обычно игнорируются при сериализации. stringifier.js Код функции на Github Очень полезная утилита, которая принимает список асинхронных функций или промисов и выполняет их друг за другом с помощью метода reduce. async-sequentializer.js Код функции на GitHub В некоторых ситуациях требуется постоянно проверять наличие обновлений. Например, при загрузке файла, или для отображения прогресса обработки заказа. Для этого используется стратегия постоянного опроса (polling), при которой некоторая функция вызывается с определенным интервалом до получения нужного результата. polling.js Код функции на GitHub Раньше не существовало нативных функций для работы с наборами промисов, поэтому приходилось изобретать собственные решения. Но Promise API постоянно развивается, и теперь у нас есть замечательные методы allSettled, race and any, с которыми стоит познакомиться каждому разработчику. Promise.all Ожидает, когда выполнятся (resolve) все промисы в списке или хотя бы один из них будет отклонен (reject). Если не было ошибок, метод вернет массив с результатами выполнения промисов, сохранив их исходный порядок. Promise.allSettled Ожидает завершения всех промисов, независимо от их статуса выполнения. Возвращает массив с результатами. Каждый объект в этом массиве имеет поле Promise.any Ожидает самого первого удачного завершения любого промиса из списка. Если ни один не завершился успешно, то вернет отклоненный промис. Promise.rase Ожидает самого первого завершения любого промиса из списка независимо от его статуса. Не то чтобы это была очень трудная задача, однако полезно знать, что в ES6 существует удобный способ ее решения. swap.js Этот трюк очень полезен при работе с состояниями в React. Ключ добавляется (или не добавляется) в объект в зависимости от некоторого условия с помощью spread-оператора. Условие задается с применением тернарного оператора. Если оно выполняется, вернется объект с ключами, которые нужно добавить в главный объект. Если не выполняется – пустой объект. Современный JavaScript также позволяет указывать строковые переменные в качестве ключей объекта. Для этого потребуются квадратные скобки: Шаблонный литерал используется для явной интерполяции строкового значения, но можно обойтись и без него: Обычно для такой проверки нужен оператор Избавиться от повторяющихся элементов массива очень просто – потребуется структура Set. Она отлично работает с разными типами данных и использует специфический алгоритм сравнения значений. С объектами это, конечно, работать не будет, так как каждый объект уникален. Поэтому нужно вводить дополнительный фильтр: Метод Array.forEach – очень удобная функциональная альтернатива обычному циклу Чтобы эмулировать это поведение, сохранив все преимущества, можно вызвать другой метод – Array.some. Он прекращает перебор элементов, если функция возвращает Деструктуризация – одна из самых мощных и удобных функций JavaScript, но синтаксис у нее довольно запутанный и сложный для запоминания. Поэтому повторяем еще раз, как задавать псевдонимы для названий полей и устанавливать значения по умолчанию: Еще две новых фичи JavaScript, которые существенно облегчают труд разработчика – optional chaining и nullish coalescing. Нулевое слияние Очень полезно при работе с неопределенными значениями. Оператор В первом примере указанное поле существует, но его значение равно Опциональные последовательности Очень удобный прием для работы со структурами с большим уровнем вложенности. В этом фрагменте кода происходит обращение к несуществующему полю объекта, поэтому выбрасывается ошибка. Чтобы избежать этого, приходится проверять проверку на каждом уровне вложенности: Чем глубже структура объекта, тем сложнее становится конструкция и тем проще в ней ошибиться. Оператор опциональных последовательностей Используя классы JavaScript, нельзя забывать, что это лишь синтаксический сахар над прототипами и функциями-конструкторами. Один из способов убедиться в этом – унаследовать класс от обычной функции-конструктора. Этот сниппет является более «натуральным» и компиляторы лучше преобразуют и сжимают его. JavaScript не поддерживает множественное наследование, позволяя расширять лишь один класс. Однако при необходимости вы можете использовать композицию и подключить столько родительских классов, сколько нужно. Всю эту логику можно поместить прямо в функцию-конструктор. Для расширения родительских классов используйте методы apply или call. Для перебора элементов разных коллекций (Object, Array, Set, String и т. д.) приходится использовать разные методы. Было бы удобно объединить всю эту функциональность в один абстрактный метод. Второй аргумент – iterables.js Код функции на Github Вы можете убедиться, что функция получила все необходимые для работы аргументы, с помощью дефолтных параметров. Если параметр не передан, будет вызвана функция required-arguments.js Код функции на Github Иногда вам требуется какой-то глобальный для всей программу объект – синглтон. Можно долго спорить о том, насколько эта практика хороша, но полезно знать, как сделать это на JavaScript. Используйте IIFE-выражение. Это функция, которая вызывается сразу же после создания. Внутри нее создается новый контекст выполнения, а значит, там можно легко изолировать все, что должно быть изолированным. singletone.js Для многоуровневого копирования объектов часто подключаются сторонние библиотеки (например, lodash). Однако эту задачу легко решить в JavaScript с помощью несложной рекурсивной функции. На каждом уровне она клонирует объекты с помощью их конструктора и копирует все поля. deep-clone.js Код функции на Github Неизменяемость данных – важная часть функциональной парадигмы программирования. В JavaScript есть метод deep-freeze.js Код функции на Github Репозиторий со всеми сниппетами1. Проверка типа
typeof
.object
. И не будем забывать про null
, который тоже object
, о чем часто забывают.
const isOfType = (() => { // создаем пустую коллекцию без прототипа const type = Object.create(null); // проверка на null type.null = x => x === null; // проверка на undefined type.undefined = x => x === undefined; // проверка на nil - сюда относятся и null, и undefined type.nil = x => type.null(x) || type.undefined(x); // проверка на строку и литерал String: // 's', "S", new String() type.string = x => !type.nil(x) && (typeof x === 'string' || x instanceof String); // проверка на число и литерал числа: 12, 30.5, new Number() // для NaN и Infinity оператор typeof также возвращает 'number', // поэтому их нужно исключить type.number = x => !type.nil(x) && ( (!isNaN(x) && isFinite(x) && typeof x === 'number') || x instanceof Number); // проверка на булево значение и литерал Boolean: // true, false, new Boolean() type.boolean = x => !type.nil(x) && (typeof x === 'boolean' || x instanceof Boolean); // проверка на массив type.array = x => !type.nil(x) && Array.isArray(x); // проверка на объект и литерал объекта: // {}, new Object(), Object.create(null) type.object = x => ({}).toString.call(x) === '[object Object]'; // проверка на конкретный тип type.type = (x, X) => !type.nil(x) && x instanceof X; // проверка на Set type.set = x => type.type(x, Set); // проверка на Map type.map = x => type.type(x, Map); // проверка на Date type.date = x => type.type(x, Date); return type; })();
2. Проверка на пустое значение
function isEmpty(x) { if (Array.isArray(x) || typeof x === 'string' || x instanceof String ) { return x.length === 0; } if (x instanceof Map || x instanceof Set) { return x.size === 0; } if (({}).toString.call(x) === '[object Object]') { return Object.keys(x).length === 0; } return false; }
3. Получение последнего элемента
function lastItem(list) { if (Array.isArray(list)) { return list.slice(-1)[0]; } if (list instanceof Set) { return Array.from(list).slice(-1)[0]; } if (list instanceof Map) { return Array.from(list.values()).slice(-1)[0]; } }
4. Генератор случайного числа в заданном диапазоне
function randomNumber(max = 1, min = 0) { if (min >= max) { return max; } return Math.floor(Math.random() * (max - min) + min); {
5. Генератор случайного id
// использование текущего времени в миллисекундах в качестве основы // увеличение при каждом запросе с помощью функции-генератора const uniqueId = (() => { const id = (function*() { let mil = new Date().getTime(); while (true) yield mil += 1; })(); return () => id.next().value; })(); // создание уникального id заданной длины // на основе полученного значения (или нуля) // увеличение при каждом запросе const uniqueIncrementingId = ((lastId = 0) => { const id = (function*() { let numb = lastId; while (true) yield numb += 1; })(); return (length = 12) => `${id.next().value}`.padStart(length, '0'); })(); // создание уникального id из букв и цифр const uniqueAlphaNumericId = (() => { const heyStack = '0123456789abcdefghijklmnopqrstuvwxyz'; const randomInt = () => Math.floor(Math.random() * Math.floor(heyStack.length)); return (length = 24) => Array.from({ length }, () => heyStack[randomInt()]).join(''); })();
6. Создание диапазона чисел
function range(maxOrStart, end = null, step = null) { if (!end) { return Array.from({length: maxOrStart}, (_, i) => i); } if (end <= maxOrStart) { return []; } if (step !== null) { return Array.from( {length: Math.ceil(((end - maxOrStart) / step))}, (_, i) => (i * step) + maxOrStart ); } return Array.from( {length: Math.ceil((end - maxOrStart))}, (_, i) => i + maxOrStart ); }
7. Форматирование JSON и сериализация данных
const obj = { name: 'John Doe', family: [ { name: 'Jane Doe', } ], something: [12, 3, 45], method() { return 'i am ignored'; }, set: new Set([1, 4, 5]), map: new Map([1, 4], [5, 10]]), symb: Symbol('test') }; const replacer = (key, val) => { if (typeof val === 'symbol') { return val.toString(); } if (val instanceof Set) { return Array.from(val); } if (val instanceof Map) { return Array.from(val.entries()); } if (typeof val === 'function') { return val.toString(); } return val; }; console.log(JSON.stringify(obj)); console.log(JSON.stringify(obj, null, 4)); console.log(JSON.stringify(obj, replacer));
8. Последовательное выполнение промисов
const asyncSequentializer = (() => { const toPromise = (x) => { // если это промис, просто вернуть его if (x instanceof Promise) { return x; } if (typeof x === 'function') { // если не асинхронная функция // то преобразовать в асинхронную, // выполнить и вернуть промис return (async () => await x())(); } return Promise.resolve(x); } // принимает список функций, асинхронных функций или промисов return (list) => { const results = []; return list .reduce((lastPromise, currentPromise) => { return lastPromise.then(res => { results.push(res); // собрать результаты return toPromise(currentPromise); }); }, toPromise(list.shift())) // собрать результаты последнего промиса // вернуть массив результатов .then(res => Promise.resolve([...results, res])); } })();
9. Постоянный опрос (polling)
async function poll(fn, validate, interval = 2500) { const resolver = async (resolve, reject) => { // отлов ошибок, выбрасываемых функцией fn try { const result = await fn(); // fn необязательно должна возвращть промис // вызов валидатора для проверки полученного результата const valid = validate(result); if (valid === true) { resolve(result); } else if (valid === false) { setTimeout(resolver, interval, resolve, reject); } // если валидатор возвращает что-то кроме true/false, // опрос прекращается } catch (e) { reject(e); } }; return new Promise(resolver); }
10. Работа с наборами промисов
status
, которое может быть равно fullfilled
или rejected
. Если промис выполнился, то результат будет в поле value
. Если же он был отклонен, то причина будет в поле reason
.
const prom1 = Promise.reject(12); const prom2 = Promise.resolve(24); const prom3 = Promise.resolve(48); const prom4 = Promise.resolve('error'); Promise.all([prom1, prom2, prom3, prom4]) .then(res => console.log('all', res)) .catch(err => console.log('all failed', err)); Promise.allSettled([prom1, prom2, prom3, prom4]) .then(res => console.log('allSettled', res)) .catch(err => console.log('allSettled failed, err)); Promise.any([prom1, prom2, prom3, prom4]) .then(res => console.log('any', res)) .catch(err => console.log('any failed, err)); Promise.race([prom1, prom2, prom3, prom4]) .then(res => console.log('race', res)) .catch(err => console.log('race failed, err));
11. Перестановка элементов массива
const array = [12, 24, 48]; // старый способ const swapOldWay = (arr, i, j) => { const arrayCopy = [...arr]; let temp = arrayCopy[i]; arrayCopy[i] = arrayCopy[j]; arrayCopy[j] = temp; return arrayCopy; }; // новый способ const swapNewWay = (arr, i, j) => { const arrayCopy = [...arr]; [arrayCopy[0], arrayCopy[2]] = [arrayCopy[2], arrayCopy[0]]; return arrayCopy; } console.log(swapOldWay(array, 0, 2)); // [48, 24, 12] console.log(swapNewWay(array, 0, 2)); // [48, 24, 12]
12. Добавление ключа в объект по условию
let condition = true; const man = { someProperty: 'some value', // условие ...(condition === true ? { newProperty: 'value' } : {}) }
13. Использование переменных в качестве ключей объекта
let property = 'newValidProp'; const man = { someProperty: 'some value', [`${property}`]: 'value', }
let property = 'newValidProp'; const man = { someProperty: 'some value', [property]: 'value', }
14. Проверка наличия ключа в объекте
in
. Однако он учитывает не только собственные свойства объекта, но также и свойства всех его прототипов (как и цикл for...in
). Это обычно не является желательным поведением. Лучше всего использовать проверку методом hasOwnProperty
.
const sample = { prop: 'value' } console.log('prop' in sample); // true console.log('toString' in sample); // true console.log(sample.hasOwnProperty('prop')); // true console.log(sample.hasOwnProperty('toString')); // false
15. Удаление дублирующихся элементов массива
const numberArrays = [ undefined, Infinity, 12, NaN, false, 5, 7, null, 12, false, 5, undefined, 89, 9, null, Infinity, 5, NaN]; console.log(Array.from(new Set(numberArrays)); // [undefined, Infinity, 12, NaN, false, 5, 7, null, 89, 9]
const objArrays = [{id: 1}, {id: 4}, {id: 1}, {id: 5}, {id: 4}]; // Set воспринимает каждый объект как уникальное значение console.log(Array.from(new Set(objArrays)); // [{id: 1}, {id: 4}, {id: 1}, {id: 5}, {id: 4}] // фильтрация по id const idSet = new Set(); console.log( objArrays.filter(obj => { const existingId = idSet.has(obj.id); idSet.add(obj.id); return !existingId; }) ); // [{id: 1}, {id: 4}, {id: 5}]
16. break и continue в цикле forEach
for
. Однако у него есть недостатки, например, отсутствие возможности прервать цикл в любой момент – по аналогии с оператором break.true
. Используем этот трюк вместо break
:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; for (const number of numbers) { if (number % 2 === 0) { continue; // переход к следующему элементу } if (number > 5) { break; } console.log(number); } // 1, 3, 5 numbers.some(number => { if (number % 2 === 0) { return false; // переход к следующему элементу } if (number > 5) { return true; // остановка перебора } console.log(number); });
17. Деструктуризация с псевдонимами и дефолтными значениями
function demo1({ dt: data }) { // переименование dt в data console.log(data); } function demo2({ dt: {name, id = 10}}) { // глубокая деструктуризация поля dt // для параметра id установлено значение по умолчанию - 10 console.log(name, id); } demo1({ dt: { name: 'sample', id: 50 } }); //{ name: 'sample', id: 50 } demo2({ dt: { name: 'sample' } }); // 'sample', 10
18. Опциональные последовательности и нулевое слияние
??
возвращает правое значение только в том случае, если левое равно null
или undefined
. То есть он сохраняет все остальные falsy-значения, например, 0 или пустую строку.
const obj = { data: { container: { name: { value: 'sample' }, int: { value: 0 } } } }; // #1 console.log(obj.data.container.int.value || 'no int value'); // no int value // #2 console.log(obj.data.container.int.value ?? 'no int value'); // будет выведено: 0
0
, что интерпретируется как falsy. Поэтому оператор ||
вернет правую часть – дефолтную строку.
const obj = { data: {} }; console.log(obj.data.wrapper.name.value); // Cannot read property 'name' of undefined
console.log( (obj && obj.data && obj.data.wrapper && obj.data.wrapper.name) || 'no name' ); // 'no name'
?
позволяет существенно сократить это выражение:
console.log((obj?.data?.wrapper?.name) || 'no name'); // no name
19. Классы – это синтаксический сахар
function Parent() { const privateProp = 12; const privateMethod = () => privateProp + 10; this.publicMethod = (x = 0) => privateMethod() + x; this.publicProp = 10; } class Child extends Parent { myProp = 20; } const child = new Child(); console.log( child.myProp, // 20 child.publicProp, // 10 child.publicMethod(40), // 62 child.privateProp, // undefined child.privateMethod(), // ошибка "child.privateMethod is not a function" );
20. Сложный конструктор
function Employee() { this.profession = 'Software Engineer'; this.salary = '$150000'; } function DeveloperFreelancer() { this.programmingLanguages = ['JavaScript', 'Python', 'Swift']; this.avgPerHour = '$100'; } function Engineer(name) { this.name = name; this.freelancer = {}; Employee.apply(this); DeveloperFreelancer.apply(this.freelancer); } const john = new Enginerr('John Doe'); console.log( john.name, // 'John Doe' john.profession, // 'Software Engineer' john.salary, // '$150000' john.freelancer, // { programmingLanguages: ['JavaScript', 'Python', 'Swift'], avgPerHour: '$100' } );
21. Итерирование всего
callback
– это функция для обработки элементов. Если она вернет true
, цикл будет прерван (по аналогии с оператором break
).
function forEach(list, callback) { const entries = Object.entries(list); let i = 0; const len = entries.length; for(;i < len; i++) { const res = callback(entries[i][1], entries[i][0], list); if(res === true) break; } } forEach([1, 2, 3], console.log); forEach(new Set([1, 2, 3]), console.log); forEach(new Map([[1, 1], [2, 2], [3, 3]]), console.log); forEach('123', console.log); forEach({ a: 1, b: 2, c: 3 }, console.log);
22. Обязательные аргументы функций
required
, которая выбросит ошибку.
function required(argName = 'param') { throw new Error(`"${argName}" is required`) } function iHaveRequiredOptions(arg1 = required('arg1'), arg2 = 10) { console.log(arg1, arg2) } iHaveRequiredOptions(); // "arg1" is required iHaveRequiredOptions(12); // 12, 10 iHaveRequiredOptions(12, 24); // 12, 24 iHaveRequiredOptions(undefined, 24); // "arg1" is required
23. Создание модулей и синглтонов
class Service { name = 'service' } const service = (function(S) { // здесь может быть подготовка данных // или любые другие действия для инициализации сервиса const service = new S(); return () => service; })(Service); const element = (function(S) { const element = document.createElement('DIV'); // здесь могут быть любые действия с DOM // например, инициализация js-плагинов return () => element; })();
24. Глубокое копирование объектов
const deepClone = obj => { let clone = obj; if (obj && typeof obj === "object") { clone = new obj.constructor(); Object.getOwnPropertyNames(obj).forEach( prop => (clone[prop] = deepClone(obj[prop])) ); } return clone; };
25. Глубокая заморозка объектов
freeze
, позволяющий “заморозить” объект, однако он действует лишь на один уровень в глубину. Для глубокой заморозки нужна рекурсия:
const deepFreeze = obj => { if (obj && typeof obj === "object") { if(!Object.isFrozen(obj)) { Object.freeze(obj); } Object.getOwnPropertyNames(obj).forEach(prop => deepFreeze(obj[prop])); } return obj; };
- 1 views
- 0 Comment
Свежие комментарии