progway — программирование, IT
2.65K subscribers
25 photos
1 video
246 links
Чат: @prog_way_chat

Разборы вопросов и задач с собеседований, мысли, полезные материалы и просто вещи, что мне интересны из мира IT

Полезности и навигация в закрепе

По всем вопросам: @denisputnov
Download Telegram
JSON Web Token

Методов авторизации и аутентификации есть много, но в последнее время большинство из них основывается на JWT.

JSON Web Token — это созданная в определенном формате base64 строка. JWT считается одним из самых безопасных и удобных форматов для передачи токенов и небольшого набора пользовательских данных с запросом.

Особенность такого формата токена в первую очередь заключается в удобстве. JWT — полностью самодостаточная единица информации. Все необходимые для идентификации данные можно хранить в самом токене, в блоке с полезной нагрузкой.

Каждый токен доступа состоит из трёх основных частей:

— Заголовок, в котором определяется информация о самом токене

— Полезная нагрузка — JSON объект, куда записываются все данные, необходимые для авторизации

— Подпись — ключ, который, пусть и не позволит зашифровать сам токен, зато дает возможность валидировать токен на изменения.

Части токена разделяются точками.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwcm9nd2F5IiwiaWF0IjoxNjg4MDMzMzI1LCJleHAiOjE3MTk1NjkzMjUsImF1ZCI6Ind3dy5wcm9nd2F5LmNvbSIsInN1YiI6InByb2d3YXkiLCJuYW1lIjoiRGVuaXMiLCJzdXJuYW1lIjoiUHV0bm92IiwiZW1haWwiOiJwcm9nd2F5QGVtYWlsLmNvbSIsInJvbGUiOiJBRE1JTiJ9.BTdcnNwZBfAHEmZEwf2P7s724Q1sZ60N2dHVRXhGtHI


Вот так это может выглядеть.

В этом токене в качестве полезной нагрузки использовался такой объект:

{
"name": "Denis",
"surname": "Putnov",
"email": "progway@email.com",
"role": "ADMIN"
}


Причём, обратите внимание на то, что чувствительную информацию в токене хранить всё равно нельзя. Не забывайте, что JWT — это всего лишь base64, поэтому декодировать его сможет любой желающий.

Да и вообще, чем меньше данных о пользователе, тем лучше.

И на этом, в целом, всё. Спасибо за прочтение, это важно для меня ❤️

#data #theory #useful
👍289🔥3🐳2🍌21🥰1
Ссылочные типы и типы значений в JavaScript

Перед прочтением я рекомендую пробежаться по ещё одному посту из канала о том, какие типы данных есть в JavaScript.

В языке JavaScript, типы данных можно разделить на две большие группы:

Типы значений — примитивы — данные, что языком воспринимаются по конкретному значению. Например, строка "Hello, World!" или число 5.

Ссылочные типы — типы данных, которые интерпретатор языка воспринимает по ссылке. Переменная больше не хранит в себе конкретное значение, зато сохраняет ссылку на место в памяти, где это самое значение хранится.

К ссылочным типам данных относятся:
— объекты
— массивы
— функции

Казалось бы, а какая разница? Но разница заключается в способе хранения и передачи этих самых данных через переменные, а так же в способе взаимодействия с ними.

Типы значений потому и называются примитивами, так как максимально просты и предсказуемы в этом плане. Такие переменные, по сути, представляют собой просто некоторую последовательность байт. При сравнении таких переменных, всё, что нужно сделать языку — посимвольно сравнить цепочки байт и выдать результат.

Ссылочные же типы, в свою очередь, могут вызвать много проблем при работе с ними. Переменная сохраняет под собой не конкретную последовательность байт, а, как и говорилось выше, лишь некоторую ссылку на ячейку памяти, в которой нужная для нас информация (байты) и записаны. Из ключевых особенностей таких типов данных сразу же приходит на ум их копирование, с чем часто возникают проблемы у новичков и не только.

Например, в этом случае мы получаем не ожидаемый для многих вывод в консоль:

const obj = {
name: "Denis",
address: {
street: 'Lenina',
apartment: 10
}
}

const clone = Object.assign({}, obj)

obj.address.street = 'Pushkina'

console.log(clone.address.street) // Pushkina


Связано это со спецификой работы Object.assign и особенностями копирования вложенных объектов. В этом случае копирование считается неглубоким, поэтому скопированный объект также изменяется.

Более подробно о понятии глубокого копирования я расскажу в отдельном посте. Он будет одним из следующих.

#javascript #data #theory #useful
🔥20👍114🐳2
Понятие глубокого копирования

В JavaScript’e, да и в других языках, часто встречаются ссылочные типы данных, вокруг которых строится одновременно много самых различных полезных хаков и не очень полезных проблем.

Понятие глубокого копирования, на самом-то деле, достаточно простое. Рассмотрим код:

const obj = {
name: "Denis",
address: {
street: 'Lenina',
apartment: 10
}
}

const clone = Object.assign({}, obj)

obj.address.street = 'Pushkina'

console.log(clone.address.street) // Pushkina


В данном случае мы получим не ожидаемый для многих вывод в консоль. Связано это с тем, что Object.assign не осуществляет глубокое копирование.

На данный момент в памяти у нас есть три объекта:
— obj
— clone
— obj.address

И, на самом деле, obj.address === clone.address. Этот вложенный объект для obj и clone на самом деле — один и тот же объект в памяти, поскольку Object.assign полностью скопировал только примитивы, а поле address просто перенёс по ссылке.

Для решения этой проблемы можно воспользоваться рекурсивными алгоритмами на основе того же Object.assign для глубокого копирования объекта. Или же, если мы точно знаем какие поля будут в нашем объекте, можем воспользоваться следующим синтаксисом через spread оператор:

const obj = {
name: "Denis",
address: {
street: "Lenina",
apartment: 10
}
};

const clone = {
...obj,
address: {
...obj.address
}
};

obj.address.street = "Pushkina";

console.log(clone.address.street); // Lenina


Тут вывод уже будет ожидаемым, поскольку мы вручную пересоздали ссылку на объект в поле address и заполнили его значениями из исходного объекта. Однако стоит быть осторожным при копировании объектов таким способом и помнить, что spread оператор не копирует геттеры и сеттеры, как и Object.assign.

Подобные проблемы и решения также можно увидеть и в контексте массивов.

#javascript #theory #data
🔥20👍113🤯3🐳2🤔1
Способы клонирования объектов

В JavaScript объекты являются ссылочным типом данных, из-за чего есть много приколов, но и проблем не меньше. Перед прочтением этого поста лучше прочитать о понятии нечистого объекта. И в этом посте мы разберёмся с тем как всё таки скопировать объект.

Итак, начать стоит с того, что нечистый объект невозможно скопировать. Именно поэтому это понятие так важно в теме копирования.

И переходя к способам, можно сказать, что их несколько основных:
1. Использование синтаксиса Spread оператора
2. Object.assign
3. Через методы глобального объекта JSON
4. Библиотека Lodash

Начать можно с конца, потому что изобретать велосипед не всегда есть смысл. В библиотеке Lodash есть несколько функций для копирования объектов, полностью протестированных и хорошо оптимизированных.

Но так же можно и использовать глобальный объект JSON, а конкретно его методы stringify и parse. Логика такая, что первый метод переводит объект в JSON строку, а второй полностью воссоздаёт его из JSON строки:

const obj = {
name: "Denis"
}

const clone = JSON.parse(JSON.stringify(obj))


Нюанс заключается лишь в том, что этот способ подходит только для самых примитивных объектов. Такой способ скопирует только примитивы, пропуская типа данных типа Symbol, геттеры, сеттеры, методы и всё, что сложнее строки, числа или boolean. Но для некоторых случаев такого способа более чем достаточно.

Более интересным вариантом является относительно новый синтаксис spread оператора, который копирует объект примерно так же как и вышеописанный способ. Spread оператор работает только с итерабельными объектами и процесс копирования тогда выглядит вот так:

const obj = {
name: "Denis"
}

const clone = { ...obj }


Тут интересным нюансом является так же то, что spread оператор не копирует геттеры и сеттеры, а также осуществляет лишь поверхностное копирование. Но всё же этот способ прекрасно подойдет для плоских объектов без лишней вложенности.

И финальным, зато самым стабильным, как мне кажется, способом, является использование Object.assign. Это метод объекта, который доставляет свойства из одного объекта в другой. Так же, как и при использовании spread оператора, геттеры и сеттеры перенесены не будут. Выглядеть это может, например, так:

const obj = {
name: "Denis"
}

const clone = Object.assign({}, obj)


Не забывайте, что способы с использованием spread оператора и Object.assign не производят глубокого копирования. Это очень важно, ведь вложенные объекты не будут по настоящему скопированы, а лишь перенесутся по ссылке. Из-за этого вы можете по ошибке мутировать какие-то данные, что в перспективе приведет к непредвиденным ошибкам.

Ну и, конечно же, каждый из способов можно использовать в зависимости от места и задачи, поэтому полезно знать про особенности каждого из них. Хотя в большинстве случаев уже классического spread оператора будет вполне достаточно.

#javascript #data #theory
👍25🔥62🐳21💯1
Преобразование унарным плюсом

Является ли преобразование унарным плюсом самым быстрым способом преобразования строки в число?

Если коротко давать ответ: да, является. Но не всё так однозначно.

Преобразование унарным плюсом, то есть записью

const string = '5'
const number = +string // 5


действительно является сравнительно более быстрым способом преобразования строки в число, чем при использовании глобальных функций parseInt или parseFloat, но стоит помнить, что такая запись может привести к неожиданным результатам, если строка содержит некорректные символы.

Кроме того, мы не всегда можем получить ожидаемый результат в, казалось бы, самых простых кейсах, например:

const string = ""

+string // 0
parseInt(string) // NaN


В этом случае, то, каким образом работает parseInt, мне кажется более ожидаемым и правильным.

Использование унарным плюсом зачастую можно считать плохой практикой и сделать выбор в пользу глобальных функций, не смотря на то, что унарный плюс работает действительно быстрее. Правда, с оговоркой, что быстрее, но не значительно. Разницу в производительности можно заметить только на невероятно больших данных.

Использование же parseInt и parseFloat даёт более ожидаемый результат и более наглядно. При беглом просмотре кода унарный плюс можно и вовсе не заметить, чего не скажешь о вызове полноценной функции.

Кстати, ещё можно преобразовать строку к числу при использовании глобального конструктора числа Number:

const string = "123"
const number = Number(string) // 123


Но такой способ будет работать дольше, чем parseInt и parseFloat.

В своей практике, я использую только глобальные функции для преобразования строки к числу. И вам советую.

#web #javascript #data
👍24🔥7🐳32
Метод массива toSorted

Относительно новые фишки языка уже просачиваются на собеседования, поэтому ближайшие посты будут посвящены разбору нововведений спецификации ECMAScript 2023. В этой спецификации было представлено много новых методов, которые позволяют упростить работу с массивами.

toSorted — это метод массива, который работает аналогично уже давно существующему методу sort, а именно, как и ожидается, сортирует массив.

Проблема обычного метода sort заключается в том что его вызов мутирует исходный массив. Это может вызвать неудобства и проблемы в тех местах, где нам запрещено менять исходные данные, например при обработке экшенов в redux.

const nums = [1, 4, 3, 2]
nums.sort()
console.log(nums) // [1, 2, 3, 4]


В этом примере в консоли мы увидим уже отсортированный массив, несмотря на то, что, казалось бы, никак не обрабатывали результат вызова метода. На самом деле, метод sort напрямую изменяет массив, для которого был вызван. Для решения такой проблемы часто можно видеть следующий хак:

const nums = [1, 4, 3, 2]
const newNums = [...nums].sort()

console.log(nums) // [1, 4, 3, 2]
console.log(newNums) // [1, 2, 3, 4]


И как раз для упрощения этой операции в спецификации и появился новый метод — toSorted. Его работу в чём то можно сравнить с методами map и filter, а именно в том, что все эти метода совершают какие-то действия только над копией массива не затрагивая изначальный, при это возвращая полностью новый массив.

На примере это будет выглядеть следующим образом:

const nums = [1, 4, 3, 2]
const newNums = nums.toSorted()

console.log(nums) // [1, 4, 3, 2]
console.log(newNums) // [1, 2, 3, 4]


Не сказал бы, что что-то невероятно сильно изменилось, но такая запись точно безопаснее, проще и визуально выглядит гораздо легче.

Аналогичные замены появились и для методов reverse и splice. Для них еще один небольшой пример:

const nums = [1, 2, 3, 4];
const reversed = nums.toReversed();
console.log(nums) // [1, 2, 3, 4]
console.log(reversed) // [4, 3, 2, 1]

const nums = [1, 2, 3, 4];
const spliced = nums.toSpliced(0, 1);
console.log(nums) // [1, 2, 3, 4]
console.log(spiced) // [2, 3, 4]


Это не все нововведения ECMAScript 2023, но это их весомая часть. Разбор остальных нововведений будет в скором времени.

Спасибо за прочтение, это важно для меня ❤️

#javascript #theory #web #data
50👍11🔥8🐳6
Двухфакторная аутентификация и факторы аутентификации

Фактор аутентификации — это способ подтверждения личности пользователя.

Существует несколько факторов аутентификации:

— Фактор знания — это информация, которую может знать конкретный пользователь, например, пароль, PIN-код или ответ на секретный вопрос

— Фактор владения — это что-то, что пользователь имеет, например, мобильное устройство для получения одноразового кода аутентификации через SMS или приложение аутентификатор

— Фактор индивидуальности — это что-то, что является частью пользователя, например, биометрические данные, такие как скан отпечатка пальца, сетчатки или параметры распознавания лица

Когда пользователь вводит свой пароль, то есть подтверждает первый фактор, система запрашивает дополнительный фактор подтверждения, чтобы убедиться в его личности. Такой подход к аутентификации называется двухфакторным.

Двухфакторная аутентификация (2FA) — подход к выполнению аутентификации пользователя, когда для идентификации личности используется сразу два фактора.

В последнее время двухфакторная аутентификация стала стандартом, так как это значимо повышает безопасность приложений, аккаунтов пользователей, а значит и безопасность их данных.

Также важно знать, что среди факторов аутентификации присутствует некая иерархия, то есть, фактор индивидуальности является более надежным, чем фактор владения, а фактор владения — более надежным, чем фактор знания.

Комбинировать различные факторы аутентификации можно в любом виде, но самые распространенные из них:
— Номер телефона + SMS — вы не просто знаете номер телефона, но и владеете им
— Почта + пароль + код из сообщения на почту — вы не просто знаете учетные данные, но и имеете доступ к почте
— Face ID — вы обладаете индивидуальными чертами лица
— Отпечаток пальца

Возможно, есть ещё какие-то распространенные связки, но, кажется, что основные я вспомнил

Также даю ссылку на свой прошлый пост, где я объяснил разницу между авторизацией и аутентификацией. Это важно в контексте этого поста.

Спасибо за прочтение, это важно для меня ❤️

#theory #data
🔥31🐳7👍6
Измерение времени в JavaScript

JavaScript предлагает несколько способов для измерения времени выполнения кода, которые могут быть полезны при оптимизации и анализе производительности. Я думаю, что все мы знаем про глобальный объект Date, который позволяет работать нам с таймстемпами, но также есть ещё один менее известный способ — Perfomance API

Как атрибут глобального объекта window, performance предоставляет пользователям лишь набор методов. И эти методы позволяют работать с производительностью более точно. Например, метод performance.now() возвращает количество миллисекунд с высокой точностью, прошедших с начала исполнения текущей страницы.

const start = performance.now();

// Код, который нужно измерить

const end = performance.now();

console.log(`Время выполнения: ${end - start} миллисекунд`);


То же самое можно сделать с использованием Date, но почему это не лучший способ?
1. perfomance.now() более точен и возвращает время с точностью до тысячных долей миллисекунды (микросекунды). Это позволяет более точно рассчитывать время выполнения, особенно если речь идёт о коротких по времени операциях.
2. Меньше накладных расходов. Date — очень тяжелый конструктор, его вызов стоит дорого. В свою очередь, Perfomance API не только занимает меньше времени, но и исключает связанные с временем вызова погрешности из расчётов. Особенно может быть полезно, если необходимо измерять время каждой итерации одного цикла, например.

Если подводить итог, то я просто за то, чтобы использовать инструменты по назначению. Perfomance API решает крайне важную и популярную задачу, это самый точный и универсальный инструмент для выполнения замеров времени. Мы всё ещё можем использовать Date для этих задач, но зачем? Это как console.log’ами дебажить. Ой.. Я ведь не один так делаю, да?)

#web #theory #data #javascript
👍23🐳151
Ещё один формат айдишников

Самыми популярными вариантами создания айдишников можно назвать uuid и целое положительное число:

6e522170-d7d5-494b-ae1f-5589608a6a51 // uuid.v4
4312 // целое положительное число


Лично я, среди этих двух вариантов, предпочёл бы использовать uuid, так как это наиболее безопасный и стабильный способ создавать айдишники, но у uuid есть два неоспоримых минуса: читаемость и длина.

Если на длину uuid ещё можно закрыть глаза, то вот читаемость порой просто убивает. Мне довелось поработать в проде с системой, которая содержит огромное количество разных сущностей, где каждая сущность ссылается на десяток других по ID, модели часто выглядели как набор ключ:айдишник. С целью улучшить читаемость таких идентификаторов, был сделан следующий шаблон:

<название модели>:<uuid>

примеры:

link:b386b8fd-64b6-4f49-85c0-b7292ec43713
building:364c2c5e-4cd2-4aec-b570-bb7e317ca018
office:56c5f243-b07f-450f-a6f0-18e6e363504c
user:f05db8bb-df2c-4575-b990-f87c3a8dd663
role:6fbdc403-0698-4e79-ade7-8a75dd6cd85b


Думаю, что на этих примерах идея более чем понятна. Мы просто дописывали к каждому айдишнику название сущности через знак двоеточия, соответственно дебажить и читать данные стало в разы проще. С какой-то стороны, мы увеличиваем длину айдишника, что тоже не хорошо, но рост размера payload’a, отдаваемого на UI, был мизерный, меньше 5%. Зато очень сильно улучшилась читаемость.

Таким образом, получаем самый безопасный, стабильный, читаемый айдишник. Не без минусов, конечно, но всё же, так ли он критичен?

Пост вдохновлён идеей одного из подписчиков канала, за что большое спасибо. Напоминаю, что моя личка открыта, обязательно приходите.

Спасибо за прочтение, это важно для меня ❤️

#web #data
👍33🐳8🔥42🍌1
Создание собственных ошибок в приложении

С ростом приложения может быть полезно создавать собственные ошибка для удобства их обработки, лебага и логирования. В этом посте поговорим о стандартных способах как это можно сделать.

Обычно ошибки выбрасываются следующим образом:

throw Error("Ошибка сервера")


Это удобно и быстро, но с ростом приложения можно оптимизировать и это. Для этого нужно создать собственный класс ошибки:

class CustomError extends Error {
constructor(message: string) {
super(message);
this.name = 'CustomError';
}
}


Обратите внимание, что нам нужно обязательно унаследовать собственную ошибку от стандартного класса Error и вызвать конструктор родительского класса через super — это необходимые шаги для правильной инициализации собственной ошибки. this.name устанавливается для того, чтобы ошибку в логах было проще идентифицировать. Это очень полезно, но не обязательно.

Далее разберем пример создания ошибок, которые могут возникнуть в работе с API:

class NullableRequestParameter extends Error {
constructor(message = 'Required req param is nullable') {
super(message)
this.name = 'NullableRequestParameter'
}
}

class Unauthorized extends Error {
constructor(message = 'User is unauthorized') {
super(message)
this.name = 'Unauthorized'
this.code = 401
}
}

export const RestServiceError = {
NullableRequestParameter,
Unauthorized
} as const

// где-то выбросим нужную нам ошибку
throw RestServiceError.Unauthorized()


Тут мы создаём две ошибки, которые далее сможем использовать в нашем приложении. Этап с объединением под RestServiceError можно опустить, это уже мой собственный код стайл. Люблю объединять общие сущности под единым началом.

Кстати, чтобы обработать конкретную ошибку определенным способом, используется следующая конструкция:

try {
// код, где может быть ошибка
} catch (error) {
switch (true) {
case error instanceof RestServiceError.Unauthorized:
// обработка ошибки Unauthorized
break;
case error instanceof RestServiceError.NullableRequestParameter:
// обработка ошибки NullableRequestParameter
break;
default:
// обработка всех непредвиденных ошибок
}
}


Конструкция со switch (true) является более предпочтительной из-за читаемости и расширяемости, хотя можно решить ту же задачу просто через if else.

На этом всё, что я хотел описать в этой теме. Надеюсь, что это было полезно. Поддержите реакциями)

Спасибо за прочтение, это важно для меня ❤️

#web #javascript #theory #data
26👍9🔥2🐳2🍌1