Понятие глубокого копирования
В JavaScript’e, да и в других языках, часто встречаются ссылочные типы данных, вокруг которых строится одновременно много самых различных полезных хаков и не очень полезных проблем.
Понятие глубокого копирования, на самом-то деле, достаточно простое. Рассмотрим код:
В данном случае мы получим не ожидаемый для многих вывод в консоль. Связано это с тем, что
На данный момент в памяти у нас есть три объекта:
И, на самом деле,
Для решения этой проблемы можно воспользоваться рекурсивными алгоритмами на основе того же
Тут вывод уже будет ожидаемым, поскольку мы вручную пересоздали ссылку на объект в поле
Подобные проблемы и решения также можно увидеть и в контексте массивов.
#javascript #theory #data
В 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👍11❤3🤯3🐳2🤔1
Способы клонирования объектов
В JavaScript объекты являются ссылочным типом данных, из-за чего есть много приколов, но и проблем не меньше. Перед прочтением этого поста лучше прочитать о понятии нечистого объекта. И в этом посте мы разберёмся с тем как всё таки скопировать объект.
Итак, начать стоит с того, что нечистый объект невозможно скопировать. Именно поэтому это понятие так важно в теме копирования.
И переходя к способам, можно сказать, что их несколько основных:
1. Использование синтаксиса Spread оператора
3. Через методы глобального объекта
4. Библиотека Lodash
Начать можно с конца, потому что изобретать велосипед не всегда есть смысл. В библиотеке Lodash есть несколько функций для копирования объектов, полностью протестированных и хорошо оптимизированных.
Но так же можно и использовать глобальный объект
Нюанс заключается лишь в том, что этот способ подходит только для самых примитивных объектов. Такой способ скопирует только примитивы, пропуская типа данных типа
Более интересным вариантом является относительно новый синтаксис
Тут интересным нюансом является так же то, что
И финальным, зато самым стабильным, как мне кажется, способом, является использование
Не забывайте, что способы с использованием
Ну и, конечно же, каждый из способов можно использовать в зависимости от места и задачи, поэтому полезно знать про особенности каждого из них. Хотя в большинстве случаев уже классического
#javascript #data #theory
В 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🔥6❤2🐳2✍1💯1
Преобразование унарным плюсом
Является ли преобразование унарным плюсом самым быстрым способом преобразования строки в число?
Если коротко давать ответ: да, является. Но не всё так однозначно.
Преобразование унарным плюсом, то есть записью
действительно является сравнительно более быстрым способом преобразования строки в число, чем при использовании глобальных функций
Кроме того, мы не всегда можем получить ожидаемый результат в, казалось бы, самых простых кейсах, например:
В этом случае, то, каким образом работает
Использование унарным плюсом зачастую можно считать плохой практикой и сделать выбор в пользу глобальных функций, не смотря на то, что унарный плюс работает действительно быстрее. Правда, с оговоркой, что быстрее, но не значительно. Разницу в производительности можно заметить только на невероятно больших данных.
Использование же
Кстати, ещё можно преобразовать строку к числу при использовании глобального конструктора числа
Но такой способ будет работать дольше, чем
В своей практике, я использую только глобальные функции для преобразования строки к числу. И вам советую.
#web #javascript #data
Является ли преобразование унарным плюсом самым быстрым способом преобразования строки в число?
Если коротко давать ответ: да, является. Но не всё так однозначно.
Преобразование унарным плюсом, то есть записью
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🐳3❤2
Метод массива toSorted
Относительно новые фишки языка уже просачиваются на собеседования, поэтому ближайшие посты будут посвящены разбору нововведений спецификации ECMAScript 2023. В этой спецификации было представлено много новых методов, которые позволяют упростить работу с массивами.
Проблема обычного метода
В этом примере в консоли мы увидим уже отсортированный массив, несмотря на то, что, казалось бы, никак не обрабатывали результат вызова метода. На самом деле, метод
И как раз для упрощения этой операции в спецификации и появился новый метод —
На примере это будет выглядеть следующим образом:
Не сказал бы, что что-то невероятно сильно изменилось, но такая запись точно безопаснее, проще и визуально выглядит гораздо легче.
Аналогичные замены появились и для методов
Это не все нововведения ECMAScript 2023, но это их весомая часть. Разбор остальных нововведений будет в скором времени.
Спасибо за прочтение, это важно для меня ❤️
#javascript #theory #web #data
Относительно новые фишки языка уже просачиваются на собеседования, поэтому ближайшие посты будут посвящены разбору нововведений спецификации 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
Фактор аутентификации — это способ подтверждения личности пользователя.
Существует несколько факторов аутентификации:
— Фактор знания — это информация, которую может знать конкретный пользователь, например, пароль, PIN-код или ответ на секретный вопрос
— Фактор владения — это что-то, что пользователь имеет, например, мобильное устройство для получения одноразового кода аутентификации через SMS или приложение аутентификатор
— Фактор индивидуальности — это что-то, что является частью пользователя, например, биометрические данные, такие как скан отпечатка пальца, сетчатки или параметры распознавания лица
Когда пользователь вводит свой пароль, то есть подтверждает первый фактор, система запрашивает дополнительный фактор подтверждения, чтобы убедиться в его личности. Такой подход к аутентификации называется двухфакторным.
Двухфакторная аутентификация (2FA) — подход к выполнению аутентификации пользователя, когда для идентификации личности используется сразу два фактора.
В последнее время двухфакторная аутентификация стала стандартом, так как это значимо повышает безопасность приложений, аккаунтов пользователей, а значит и безопасность их данных.
Также важно знать, что среди факторов аутентификации присутствует некая иерархия, то есть, фактор индивидуальности является более надежным, чем фактор владения, а фактор владения — более надежным, чем фактор знания.
Комбинировать различные факторы аутентификации можно в любом виде, но самые распространенные из них:
— Номер телефона + SMS — вы не просто знаете номер телефона, но и владеете им
— Почта + пароль + код из сообщения на почту — вы не просто знаете учетные данные, но и имеете доступ к почте
— Face ID — вы обладаете индивидуальными чертами лица
— Отпечаток пальца
Возможно, есть ещё какие-то распространенные связки, но, кажется, что основные я вспомнил
Также даю ссылку на свой прошлый пост, где я объяснил разницу между авторизацией и аутентификацией. Это важно в контексте этого поста.
Спасибо за прочтение, это важно для меня ❤️
#theory #data
🔥31🐳7👍6
Измерение времени в JavaScript
JavaScript предлагает несколько способов для измерения времени выполнения кода, которые могут быть полезны при оптимизации и анализе производительности. Я думаю, что все мы знаем про глобальный объект
Как атрибут глобального объекта
То же самое можно сделать с использованием
2. Меньше накладных расходов.
Если подводить итог, то я просто за то, чтобы использовать инструменты по назначению. Perfomance API решает крайне важную и популярную задачу, это самый точный и универсальный инструмент для выполнения замеров времени. Мы всё ещё можем использовать
#web #theory #data #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🐳15❤1
Ещё один формат айдишников
Самыми популярными вариантами создания айдишников можно назвать
Лично я, среди этих двух вариантов, предпочёл бы использовать
Если на длину
Думаю, что на этих примерах идея более чем понятна. Мы просто дописывали к каждому айдишнику название сущности через знак двоеточия, соответственно дебажить и читать данные стало в разы проще. С какой-то стороны, мы увеличиваем длину айдишника, что тоже не хорошо, но рост размера
Таким образом, получаем самый безопасный, стабильный, читаемый айдишник. Не без минусов, конечно, но всё же, так ли он критичен?
Пост вдохновлён идеей одного из подписчиков канала, за что большое спасибо. Напоминаю, что моя личка открыта, обязательно приходите.
Спасибо за прочтение, это важно для меня ❤️
#web #data
Самыми популярными вариантами создания айдишников можно назвать
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🔥4❤2🍌1
Создание собственных ошибок в приложении
С ростом приложения может быть полезно создавать собственные ошибка для удобства их обработки, лебага и логирования. В этом посте поговорим о стандартных способах как это можно сделать.
Обычно ошибки выбрасываются следующим образом:
Это удобно и быстро, но с ростом приложения можно оптимизировать и это. Для этого нужно создать собственный класс ошибки:
Обратите внимание, что нам нужно обязательно унаследовать собственную ошибку от стандартного класса
Далее разберем пример создания ошибок, которые могут возникнуть в работе с
Тут мы создаём две ошибки, которые далее сможем использовать в нашем приложении. Этап с объединением под
Кстати, чтобы обработать конкретную ошибку определенным способом, используется следующая конструкция:
Конструкция со
На этом всё, что я хотел описать в этой теме. Надеюсь, что это было полезно. Поддержите реакциями)
Спасибо за прочтение, это важно для меня ❤️
#web #javascript #theory #data
С ростом приложения может быть полезно создавать собственные ошибка для удобства их обработки, лебага и логирования. В этом посте поговорим о стандартных способах как это можно сделать.
Обычно ошибки выбрасываются следующим образом:
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
Что такое мемоизация
Мемоизация — техника оптимизации кода, сокращающая время его исполнения.
Представим, что у нас есть функция
1. Вызовем функцию, подождём 10 секунд и сохраним результат её выполнения в кэш
2. Вызовем функцию ещё раз с теми же аргументами. Вместо того, чтобы исполнять тяжелый код, обратимся к кэшу и проверим нет ли там нужного для нас результат вычислений
3. Если нужный результат есть, вернём его. Если нет, см. шаг 1
Любую ли функцию можно мемоизировать? Конечно же нет. Мемоизировать можно только чистые функции. О том, что это такое, я писал отдельный пост.
Также стоит учитывать, что со временем жизни программы кэш может очень сильно разрастись. Из-за этого сама программа может занимать очень много памяти. Решением может быть несколько подходов:
1. Установить время жизни для каждого из значение кэша
2. Подход LRU (Last Recently Used), когда мы удаляем из кэша значения, которые дольше всего не запрашивались. Такой подход позволяет сохранять в кэше только самые часто используемые данные.
3. Ограничение размера кэша, то есть, когда кэш достигает порогового значения по количеству записей или памяти, старые записи удаляются для освобождения места под новые
4. Просто удалять весь кэш по какому-то таймауту
Сложность тут в том, что нам необходимо реализовать собственный кэш с поддержкой всех необходимых апи для того, что мы описали выше. В JavaScript, например, LRU кэша из коробки, то есть его придётся написать самостоятельно. Самая примитивная же форма кэша для мемоизации, которая и используется чаще всего —
Если нет желания писать свой велосипед, что прекрасно, можно использовать готовые реализации, например, из библиотеки
В следующем посте рассмотрим реализацию примитивной мемоизации в коде.
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #theory #javascript #patterns #data
Мемоизация — техника оптимизации кода, сокращающая время его исполнения.
Представим, что у нас есть функция
foo
, которая выполняется 10 секунд. Нам нужно вызвать эту функцию 3 раза подряд с одними и теми же аргументами. Несложно посчитать, что такой код будет выполняться 30 секунд. Но мы можем применить мемоизацию:1. Вызовем функцию, подождём 10 секунд и сохраним результат её выполнения в кэш
2. Вызовем функцию ещё раз с теми же аргументами. Вместо того, чтобы исполнять тяжелый код, обратимся к кэшу и проверим нет ли там нужного для нас результат вычислений
3. Если нужный результат есть, вернём его. Если нет, см. шаг 1
Любую ли функцию можно мемоизировать? Конечно же нет. Мемоизировать можно только чистые функции. О том, что это такое, я писал отдельный пост.
Также стоит учитывать, что со временем жизни программы кэш может очень сильно разрастись. Из-за этого сама программа может занимать очень много памяти. Решением может быть несколько подходов:
1. Установить время жизни для каждого из значение кэша
2. Подход LRU (Last Recently Used), когда мы удаляем из кэша значения, которые дольше всего не запрашивались. Такой подход позволяет сохранять в кэше только самые часто используемые данные.
3. Ограничение размера кэша, то есть, когда кэш достигает порогового значения по количеству записей или памяти, старые записи удаляются для освобождения места под новые
4. Просто удалять весь кэш по какому-то таймауту
Сложность тут в том, что нам необходимо реализовать собственный кэш с поддержкой всех необходимых апи для того, что мы описали выше. В JavaScript, например, LRU кэша из коробки, то есть его придётся написать самостоятельно. Самая примитивная же форма кэша для мемоизации, которая и используется чаще всего —
Map
или же обычный объект.Если нет желания писать свой велосипед, что прекрасно, можно использовать готовые реализации, например, из библиотеки
lodash
— вот исходники функцииВ следующем посте рассмотрим реализацию примитивной мемоизации в коде.
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #theory #javascript #patterns #data
👍30🐳5🔥4❤2
Реализация мемоизации в JavaScript
О том, что такое мемоизация, я рассказывал в прошлом посте.
На самом деле, всё это довольно просто. Попробуем написать функцию-обёртку
Наша функция-обёртка будет принимать только целевую функцию, которую мы хотим мемоизировать. В качестве кэша будем использовать
И далее просто воспользуемся замыканием, чтобы ограничить доступ к созданному кэшу только для целевой функции. Для этого вернем из
Полный код функции:
Я осознанно не использовал TypeScript, так как он сильно визуально усложняет эту функцию и объяснений получается много. Натыкайте 🔥 на пост и я разберу как правильно типизировать такую функцию
@prog_way_blog — чат — #theory #code #javascript #data
О том, что такое мемоизация, я рассказывал в прошлом посте.
На самом деле, всё это довольно просто. Попробуем написать функцию-обёртку
memoize
, с помощью которой можно мемоизировать любую другую функцию. Начнём как всегда с интерфейса:function memoize(func) {
// ...
}
Наша функция-обёртка будет принимать только целевую функцию, которую мы хотим мемоизировать. В качестве кэша будем использовать
Map
:const cache = new Map();
И далее просто воспользуемся замыканием, чтобы ограничить доступ к созданному кэшу только для целевой функции. Для этого вернем из
memoize
новую функцию:return function(...args) {
// ключ сериализуем в строку, чтобы
// не было проблем с аргументами
// объектами, массивами и т.д.
const key = JSON.stringify(args);
// проверяем есть ли результат в кэше
if (!cache.has(key)) {
// если нет, то сохраняем в кэш
// значение вызова функции
cache.set(key, func(...args));
}
// и возвращаем значение из кэша
return cache.get(key);
};
Полный код функции:
function memoize(func) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (!cache.has(key)) {
cache.set(key, func(...args));
}
return cache.get(key);
};
}
Я осознанно не использовал TypeScript, так как он сильно визуально усложняет эту функцию и объяснений получается много. Натыкайте 🔥 на пост и я разберу как правильно типизировать такую функцию
@prog_way_blog — чат — #theory #code #javascript #data
🔥76👍5🐳2❤1😁1
Что такое Callback Hell и как с ним бороться?
Частый вопрос с собеса, особенно если идти куда-то повыше стажёра.
Callback Hell — это ситуация в асинхронном программировании, когда вложенные друг в друга функции обратного вызова (он же callback) образуют "лесенку", что делает код трудным для чтения и сопровождения.
Как с этим можно бороться?
Конечно же промисы и async/await синтаксис:
Промисы позволяют писать асинхронный код более линейно и читаемо благодаря цепочке вызовов (chaining):
А async/await — это более современный синтаксис над промисами, который так же позволяет развернуть вложенные колбеки в плоский код:
И о Promise, и о async/await у меня уже есть более подробные посты
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #theory #code #javascript #patterns #data
Частый вопрос с собеса, особенно если идти куда-то повыше стажёра.
Callback Hell — это ситуация в асинхронном программировании, когда вложенные друг в друга функции обратного вызова (он же callback) образуют "лесенку", что делает код трудным для чтения и сопровождения.
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doAnotherThing(newResult, function(finalResult) {
doSomethingMore(finalResult, function(lastResult) {
console.log(lastResult);
});
});
});
});
Как с этим можно бороться?
Конечно же промисы и async/await синтаксис:
Промисы позволяют писать асинхронный код более линейно и читаемо благодаря цепочке вызовов (chaining):
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doAnotherThing(newResult))
.then(finalResult => console.log(finalResult))
.catch(error => console.error(error));
А async/await — это более современный синтаксис над промисами, который так же позволяет развернуть вложенные колбеки в плоский код:
async function process() {
try {
const result = await doSomething();
const newResult = await doSomethingElse(result);
const finalResult = await doAnotherThing(newResult);
console.log(finalResult);
} catch (error) {
console.error(error);
}
}
process();
И о Promise, и о async/await у меня уже есть более подробные посты
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #theory #code #javascript #patterns #data
🔥18❤9👍9🐳3
Что такое CI/CD
Помимо того, что разработчики должны уметь писать сам код, было бы неплохо уметь автоматизировать его публикацию и все попутные процессы, что с этим связаны — тестирование, сборка…
Для этого есть CI/CD — набор практик, который позволяет автоматизировать практически всё, кроме написания самого кода.
🟢 CI — Continuous Integration — это всё, что касается интеграции нового кода в репозиторий. В основном, это автоматизация сборки, тестирования и разные проверки в коде, типа eslint, prettier или biome.
🟢 CD — Continuous Delivery (иногда расшифровывают как Continuous Deployment) — это всё, что связано с доставкой готового собранного образа вашего приложения на какое-то окружение (сервер) и его запуск для дальнейшей работы. Ведь собрать приложение недостаточно для его публикации — сборку нужно ещё куда-то загрузить и как-то запустить.
Если поверхностно, CI/CD — это именно об этом. Думаю, что сделаю ещё какие-то более подробные посты в будущем, если это будет актуально.
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #theory #data #useful
Помимо того, что разработчики должны уметь писать сам код, было бы неплохо уметь автоматизировать его публикацию и все попутные процессы, что с этим связаны — тестирование, сборка…
Для этого есть CI/CD — набор практик, который позволяет автоматизировать практически всё, кроме написания самого кода.
Основное отличие от ручного способа заключается лишь в том, что все эти процессы выполняются не на компьютерах разработчиков, а где-то там далеко на серверах GitHub или типа того, всё зависит от архитектуры вашей системы контроля версий.
Если поверхностно, CI/CD — это именно об этом. Думаю, что сделаю ещё какие-то более подробные посты в будущем, если это будет актуально.
Если коротко:
CI — процессы, связанные с интеграцией кода в репозиторий
CD — процессы, связанные с доставкой готовой сборки на окружение
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #theory #data #useful
Please open Telegram to view this post
VIEW IN TELEGRAM
❤28👍10🐳4🔥1👀1
Как отменить уже отправленный HTTP запрос?
Для отмены уже отправленного запроса нам пригодится встроенный в JavsScript объект —
Этот объект позволяет отменять уже запущенные асинхронные операции,
Нужно это много где, я приведу самый очевидный пример с реактом:
Представим, что пользователь открывает страницу. На странице в
Использование🌚
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #theory #javascript #code #data
Для отмены уже отправленного запроса нам пригодится встроенный в JavsScript объект —
AbortController
Этот объект позволяет отменять уже запущенные асинхронные операции,
fetch
в том числеconst controller = new AbortController()
// отправляем запрос
fetch('https://.../', { signal: controller.signal })
// отменяем его
controller.abort()
Нужно это много где, я приведу самый очевидный пример с реактом:
Представим, что пользователь открывает страницу. На странице в
useEffect
идёт запрос к API, но пользователь, не дожидаясь ответа от сервера, переходит на другую страницу. Запрос есть, трафик занят, есть риск нарушения жизненного цикла компонента, а результаты этого запроса уже и вовсе не нужны. Вот так это решается:useEffect(() => {
const controller = new AbortController()
// делаем запрос на маунт компонента
fetch('https://.../', { signal: controller.signal })
// отменяем запрос на анмаунт компонента
return () => controller.abort()
}, [])
Использование
AbortController'a
помогает избежать потенциальных утечек памяти и гарантирует, что запросы не будут выполняться после того, как компонент был размонтирован. Полезно это при любых запросах, так что можно смело сделать свой хук обёртку. Или просто использовать @tanstack/react-query
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #theory #javascript #code #data
Please open Telegram to view this post
VIEW IN TELEGRAM
👍32❤11🔥3🐳2🤯1🤩1
Как браузер понимает, что нужно закешировать?
Частый вопрос с собесов, который, к тому же, часто может пригодиться и на практике
Некоторые современные браузеры стараются кешировать файлы опираясь на частоту их использования и изменения. То есть, чтобы кеширование хоть как-то начало работать, браузеру необходимо собрать некоторую историю ваших посещений и загрузок ресурсов для открытия сайтов. Сам по себе этот способ не очень надежный, про него мало кто знает и рассчитывают на него достаточно редко
Самый простой способ контролировать кеширование в браузере — это напрямую сказать браузеру что можно кешировать и как долго это можно сделать. Реализуют это обычно через специальные HTTP заголовки
—
—
—
Также есть ещё один заголовок —
Обычно
Если нужно кешировать ресурс по времени, то обычно используют
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #theory #web #data
Частый вопрос с собесов, который, к тому же, часто может пригодиться и на практике
Кеширование — это способ оптимизации загрузки через хранение копий файлов на вашем устройстве, чтобы ускорить доступ к ним в будущем. Это могут быть HTML, CSS, JS файлы, изображения, шрифты и всё остальное
Некоторые современные браузеры стараются кешировать файлы опираясь на частоту их использования и изменения. То есть, чтобы кеширование хоть как-то начало работать, браузеру необходимо собрать некоторую историю ваших посещений и загрузок ресурсов для открытия сайтов. Сам по себе этот способ не очень надежный, про него мало кто знает и рассчитывают на него достаточно редко
Самый простой способ контролировать кеширование в браузере — это напрямую сказать браузеру что можно кешировать и как долго это можно сделать. Реализуют это обычно через специальные HTTP заголовки
Cache-Control
и Expires
:Cache-Control
заголовок может иметь несколько значений:—
no-cache
— браузер проверяет актуальность ресурса на сервере при каждом запросеЭто значение не означает, что браузер вообще не должен кешировать ресурс, как это может показаться на первый взгляд.
На самом деле, ресурс все равно может быть сохранен в кеше, но с этим заголовком браузер обязан каждый раз перед его использованием обращаться к серверу, чтобы проверить, не изменилась ли версия ресурса.
Чаще всего это значение используют с ресурсами, что изменяются часто
—
no-store
— полностью запрещает хранить ресурс в кеше. Особенно полезно для чувствительных данных, типа истории переводов в банке—
public, max-age=31536000
— пример агрессивного кеширования, где ресурс можно хранить в кеше до одного года (максимальное время max-age указывается в секундах). Например, это может быть применено к логотипу компании, который редко меняетсяТакже есть ещё один заголовок —
Expires
. Он указывает точную дату и время, до которого ресурс считается актуальным и может быть использован из кеша без проверки на сервере:Expires: Wed, 21 Oct 2024 07:28:00 GMT
Этот заголовок говорит браузеру, что ресурс может использоваться
из кеша до 21 октября 2024 года 07:28:00 по времени GMT
Обычно
Expires
используется реже из-за того, что он работает именно с абсолютными датами. Если ваши серверные часы или время на устройстве пользователя настроены неправильно, это может привести к некорректному кешированиюЕсли нужно кешировать ресурс по времени, то обычно используют
Cache-Control
и его параметр max-age
, так как в этом случае дата окончания кеширования рассчитывается относительно настроек времени на устройстве пользователяКратко:
Чаще всего браузер понимает что кешировать через специальный заголовок Cache-Control
Спасибо за прочтение, это важно для меня ❤️
@prog_way_blog — чат — #theory #web #data
👍40🐳10🔥8❤3
Как скопировать значение в буфер обмена
Часто может возникнуть необходимость скопировать какое-то значение в буфер обмена, например, при нажатии на кнопку, и есть два способа сделать это:
Современный метод использует
Этот метод прост, но есть один важный нюанс: он работает только в безопасных контекстах (например, на страницах, загруженных по HTTPS). Проверить это можно с помощью флага
До появления
Комбинацией обоих способов можно покрыть абсолютно все кейсы во всех браузерах:
Спасибо за прочтение, это важно для меня❤️
@prog_way_blog — чат — #web #javascript #theory #data
Часто может возникнуть необходимость скопировать какое-то значение в буфер обмена, например, при нажатии на кнопку, и есть два способа сделать это:
Современный метод использует
navigator.clipboard
. Это браузерное API, которое предоставляет асинхронные методы для чтения и записи данных в буфер обменаnavigator.clipboard.writeText('Какой-то текст')
Этот метод прост, но есть один важный нюанс: он работает только в безопасных контекстах (например, на страницах, загруженных по HTTPS). Проверить это можно с помощью флага
window.isSecureContext
. Если страница не является безопасной, вызов методов из navigator.clipboard
вызовет ошибкуДо появления
navigator.clipboard
использовался метод document.execCommand('copy')
. Он требует немного больше манипуляций с DOM
, но работает в небезопасных контекстах и даже самых старых браузерах:// нужно создать какой-то текстовый элемент
// и установить ему необходимое значение
const textArea = document.createElement('textarea');
textArea.value = "Какой-то текст";
// убрать элемент куда-то далеко
textArea.style.position = 'absolute';
textArea.style.left = '-999999px;
// и добавить его в вёрстку
document.body.prepend(textArea);
// далее выделить наше поле ввода
textArea.select();
try {
// и скопировать значение в буфер обмена
document.execCommand('copy');
console.log('Текст скопирован!');
} catch (err) {
console.error('Не удалось скопировать текст: ', err);
}
// не забываем удалить элемент из вёрстки
textArea.remove()
Комбинацией обоих способов можно покрыть абсолютно все кейсы во всех браузерах:
if (navigator.clipboard && window.isSecureContext) {
// используем navigator.clipboard
} else {
// используем document.execCommand('copy')
}
Кратко:
— в современных браузерах используется браузерное API navigator.clipboard для взаимодействия с буфером
— в старых браузерах и на страницах, работающих по http, используется устаревший document.execCommand
Спасибо за прочтение, это важно для меня
@prog_way_blog — чат — #web #javascript #theory #data
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥38👍5🐳5❤4
Теги для шаблонных строк
В JavaScript есть, как по мне, крайне странный синтаксис. Самым очевидным его применением можно считать
В результате выполнения этого чуда мы получим компонент на основе нативного
Но вы когда нибудь задумывались, что
На самом деле, самый базовый пример такого синтаксиса можно рассмотреть так:
Всё, что делает эта функция — собирает строку из шаблона и подставленных переменных
—
—
Попробуем вызвать нашу функцию:
Использование обратных кавычек после именования функции вызывает эту самую функцию
По такому же принципу и работает
Этот синтаксис очень специфичный, ему не так много применений, но всё таки в некоторых случаях он бывает очень удобен. Например, в призме, с помощью такого такого синтаксиса можно кинуть запрос в БДшку
В реальной жизни вам скорее всего не понадобится писать подобные функции, но вдруг..
Спасибо за прочтение, это важно для меня❤️
@prog_way_blog — чат — #web #javascript #theory #data #code
В JavaScript есть, как по мне, крайне странный синтаксис. Самым очевидным его применением можно считать
styled-components
и выглядит всё это примерно так:const display = 'flex';
const Button = styled.button`
padding: 10px;
color: red;
display: ${display}
`
В результате выполнения этого чуда мы получим компонент на основе нативного
button
с предустановленными стилями из литераловНо вы когда нибудь задумывались, что
styled.button
— это тоже функция? А как она вызывается? Как устроена внутри?На самом деле, самый базовый пример такого синтаксиса можно рассмотреть так:
function foo(strings, ...values) {
let result = strings[0];
values.forEach((value, index) => {
result += value + strings[index + 1];
});
return result;
}
Всё, что делает эта функция — собирает строку из шаблона и подставленных переменных
—
strings
— массив строк, содержащий все части текста, разделенные переменными—
values
— массив значений, которые вставляются внутрь шаблонаПопробуем вызвать нашу функцию:
const name = "Денис"
const channel = "progway"
foo`Меня зовут ${name} и я люблю ${channel}`
Использование обратных кавычек после именования функции вызывает эту самую функцию
Для нашего примера, strings — это:
[
"Меня зовут ",
" и я люблю ",
""
]
а values:
[
"Денис",
"progway"
]
По такому же принципу и работает
styled-components
, конечно же, с более сложной логикой внутриЭтот синтаксис очень специфичный, ему не так много применений, но всё таки в некоторых случаях он бывает очень удобен. Например, в призме, с помощью такого такого синтаксиса можно кинуть запрос в БДшку
В реальной жизни вам скорее всего не понадобится писать подобные функции, но вдруг..
Спасибо за прочтение, это важно для меня
@prog_way_blog — чат — #web #javascript #theory #data #code
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👍9❤8😐2🥰1🐳1
Связываем React и localStorage через useSyncExternalStore
Как согласовать изменение состояния в реакте и поля в
Также можно обработать какое-то не-реактовое значение через комбинацию
Но не так давно в 18 версию
Многие скипнули его и даже не знают зачем он нужен, что, в целом, достаточно ожидаемо, ведь даже команда разработчиков позиционировала его больше как хук для разработчиков библиотек, а мы тут далеко не все пишем свои либы
Короче, что это за хук вообще? Очень просто — этот хук нужен для более глубокой интеграции внешних хранилищ в модель React. Говоря проще — хук нужен для того, чтобы триггерить рендер из внешних хранилищ, а не только через
Как раз этот хук и поможет нам интегрироваться с
На коленке код будет выглядеть примерно так:
В чём тут идея:
1. При вызове
2. В функции подписки
Использовать будем как обычный
Теперь хук при вызове с одним и тем же ключом к
Используя тот же подход, можно реализовать порой очень полезные хуки
Спасибо за прочтение, это важно для меня❤️
@prog_way_blog — чат — #web #javascript #theory #data #code #react
Как согласовать изменение состояния в реакте и поля в
localStorage
? До недавнего времени самым простым вариантом было создать контекст с внутренним React состоянием и обрабатывать всё взаимодействие с localStorage
через него — вариант рабочий, но далеко не идеален: легко напороться на ререндеры, много кода писать нужно ну и вот это вот всёТакже можно обработать какое-то не-реактовое значение через комбинацию
useState
+ useEffect
, но это ещё менее надёжно, ведь браузерные значения могут меняться и без уведомления реакта, и, соответственно, без ререндераКрасиво в одной из статей на хабре описали:
Для работы с состоянием в React используются хуки useState и useReducer, но они не умеют работать с состоянием, которое "живет" за пределами React, поскольку в один момент времени доступна только одна версия внешнего состояния.
Значения внешнего состояния могут меняться со временем без ведома React, что может приводить к таким проблемам, как отображение двух разных значений для одних и тех же данных.
Статья: https://habr.com/ru/companies/timeweb/articles/720136/
Но не так давно в 18 версию
React
добавили хук useSyncExternalStore
, который такую задачу решает намного изящнееМногие скипнули его и даже не знают зачем он нужен, что, в целом, достаточно ожидаемо, ведь даже команда разработчиков позиционировала его больше как хук для разработчиков библиотек, а мы тут далеко не все пишем свои либы
Короче, что это за хук вообще? Очень просто — этот хук нужен для более глубокой интеграции внешних хранилищ в модель React. Говоря проще — хук нужен для того, чтобы триггерить рендер из внешних хранилищ, а не только через
setState
функции Как раз этот хук и поможет нам интегрироваться с
localStorage
сильно проще и безопаснее. Тут localStorage
в понятие внешнего хранилища ложится просто шикарноНа коленке код будет выглядеть примерно так:
const useLocalStorageState = (key: string, defaultValue?: string) => {
const subscribe = (listener: () => void) => {
window.addEventListener("update-local-storage", listener);
return () => void window.removeEventListener("update-local-storage", listener);
};
const getSnapshot = () => localStorage.getItem(key) ?? defaultValue;
const store = useSyncExternalStore(subscribe, getSnapshot);
const updateStore = (newValue: string) => {
localStorage.setItem(key, newValue);
window.dispatchEvent(new StorageEvent("update-local-storage", { key, newValue }));
};
return [store, updateStore] as const;
};
В чём тут идея:
1. При вызове
updateStore
будем помимо изменения значения в localStorage
диспатчить на window
ещё и StorageEvent
с ключом, например, "update-local-storage"
2. В функции подписки
subscribe
объясним когда нужно вызывать getSnapshot
для получения актуального состояния из внешнего хранилища и когда от его прослушивания нужно отписаться. Можно воспринимать как эффектИспользовать будем как обычный
useState
:const [name, setName] = useLocalStorageState("name", "progway");
Теперь хук при вызове с одним и тем же ключом к
localStorage
(name
в примере выше) будет обновлять все зависимые компоненты при регистрации события "update-local-storage"
на window
Используя тот же подход, можно реализовать порой очень полезные хуки
useMediaQuery
, useWindowSize
и другие. О первых двух можно прочитать в статье от Timeweb CloudСпасибо за прочтение, это важно для меня
@prog_way_blog — чат — #web #javascript #theory #data #code #react
Please open Telegram to view this post
VIEW IN TELEGRAM
❤28👍11🔥6🐳2
Галопом по теории WebRTC
Тема большая, но попробую максимально сжато рассказать в текстовом посте
Причем акцент тут на фразе "прямая передача", так как
Однако, если мы можем общаться между устройствами без посредника, это не значит, что мы можем установить соединение без посредника
Для того чтобы установить прямое соединение между двумя клиентами, нужно знать их IP адреса. А мы, как пользователи какого-нибудь ноутбука не можем напрямую узнать IP адрес ноутбука нашего друга, потому что скорее всего у его устройства нет публичного статичного IP адреса
У каждого устройства в сети гарантированно может быть только локальный IP адрес, который далее через NAT транслируется во внешнюю сеть
Короче, чтобы решить всю эту сетевую кашу, существует STUN, который позволяет каждому клиенту узнать свой публичный адрес и инициатору соединения сформировать две сущности —
Далее две эти сущности отправляются сигнальному серверу, и уже через сигнальный сервер инициатор получит
Прямое соединение откроется только после выполнения всех этих шагов, но для открытия соединения нужен сервер-посредник
Немного задушил теорией и надеюсь, что нигде не ошибся, пишите если что
Спасибо за прочтение, это важно для меня❤️
@prog_way_blog — чат — #web #theory #data
Тема большая, но попробую максимально сжато рассказать в текстовом посте
WebRTC — Web Real Time Communications — стандарт, который описывает прямую передачу потоковых аудиоданных, видеоданных и контента между клиентами в режиме реального времени. На основе этого стандарта работают всякие зумы, телемосты, google meet и прочие площадки
Для реализации такого соединения в браузерах используется нативный JS класс RTCPeerConnection
Причем акцент тут на фразе "прямая передача", так как
WebRTC
в идеале, пусть и не всегда, представляет собой p2p
соединениеp2p — peer-to-peer — это такое сетевое соединение, которое позволяет двум или более устройствам общаться между собой напрямую без сервера-посредника. Ещё такие соединения называют одноранговыми
Однако, если мы можем общаться между устройствами без посредника, это не значит, что мы можем установить соединение без посредника
Для того чтобы установить прямое соединение между двумя клиентами, нужно знать их IP адреса. А мы, как пользователи какого-нибудь ноутбука не можем напрямую узнать IP адрес ноутбука нашего друга, потому что скорее всего у его устройства нет публичного статичного IP адреса
У каждого устройства в сети гарантированно может быть только локальный IP адрес, который далее через NAT транслируется во внешнюю сеть
NAT — Network Address Translation — механизм TCP/IP, который позволяет транслировать внутренние локальные IP адреса во внешние IP адреса за пределами нашего маршрутизатора (можно назвать роутером), который будет выступать в роли межсетевого экрана (файрвол)
Короче, чтобы решить всю эту сетевую кашу, существует STUN, который позволяет каждому клиенту узнать свой публичный адрес и инициатору соединения сформировать две сущности —
Offer
и ICE Candidate
STUN — Session Traversal Utilities for NAT (перевод "утилиты обхода сеансов для NAT") — это протокол, который позволяет каждому устройству узнать свой внешний IP адрес даже за файрволом
Клиент отправляет STUN серверу запрос, затем сервер STUN отправляет клиенту обратно информацию о том, каков внешний адрес маршрутизатора NAT, и какой порт открыт на NAT для приема входящих запросов обратно во внутреннюю сеть
Offer
— это предложение открыть прямое соединение на основе параметров связи, то есть Offer
описывает используемые кодеки для передачи контента, информацию о медиа-потоках (видео, аудио) и тдICE Candidate
— это метаданные устройства в p2p
сети: его IP, порт и прочие параметрыДалее две эти сущности отправляются сигнальному серверу, и уже через сигнальный сервер инициатор получит
Answer
(то же самое, что и Offer, только от второго клиента)Прямое соединение откроется только после выполнения всех этих шагов, но для открытия соединения нужен сервер-посредник
Сигнальный сервер — это то, что мы можем реализовать сами на любых удобных технологиях. Тут вообще не важно как Offer, Answer и ICE Candidate будут передаваться между клиентами — REST API, сокеты... хоть голубями отправляйте
Немного задушил теорией и надеюсь, что нигде не ошибся, пишите если что
Спасибо за прочтение, это важно для меня
@prog_way_blog — чат — #web #theory #data
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥24👍7❤5🐳3
Что такое Server-Sent Events
Часто
1. Нам нужно постоянно получать обновления с сервера
2. Не нужно постоянно отправлять что-то с клиента
Фикус в том, что держать
Для реализации понадобится только простенький эндпоинт на сервере, а далее процесс выглядит так:
1. Клиент делает
2. Сервер создаёт
3. Клиент подписывается на новое сообщение в стриме
На сервере это будет выглядеть примерно так:
На клиенте это будет выглядеть примерно так:
С таким кодом мы будем получать на клиенте сообщение "ПРИВЕТ!" каждую секунду
При этом, конечно же, никто не мешает усложнить логику со стороны сервера, и пушить новые сообщения в стрим не каждую секунду, а только при изменении данных
И конечно же никто не запрещает обернуть стрим в какой-нибудь React хук и сделать дженеричное решение для всего проекта/проектов
Если вы ни разу не работали SSE, то очень рекомендую потыкать хотя бы в песочнице — очень крутая штука!
Спасибо за прочтение, это важно для меня🥰
@prog_way_blog — чат — #theory #javascript #code #data #web
SSE
— это технология для однонаправленного соединения между сервером и клиентом, которая позволяет серверу отправлять обновления данных в реальном времениЧасто
SSE
могут стать отличной альтернативой WebSocket
. Он отлично подойдёт для кейсов, когда:1. Нам нужно постоянно получать обновления с сервера
2. Не нужно постоянно отправлять что-то с клиента
Такая односторонняя связь полезна при реализации:
— уведомлений
— обновления данных в реальном времени (цен, загрузки CPU...)
— индикатора прогресса загрузки большого файла
— даже в играх
И многих других случаях
Фикус в том, что держать
SSE
гораздо проще и дешевле, чем держать WebSocket
. Как по коду, так и по перфомансуДля реализации понадобится только простенький эндпоинт на сервере, а далее процесс выглядит так:
1. Клиент делает
GET
запрос на подготовленный эндпоинт через EventStream
2. Сервер создаёт
event-stream
, просто устанавливая нужный заголовок. Соединение не закрывается, и с этого момента сервер может пушить в стрим любые строковые данные3. Клиент подписывается на новое сообщение в стриме
На сервере это будет выглядеть примерно так:
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/stream') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
setInterval(() => {
res.write('data: ПРИВЕТ!\n\n');
}, 1000);
}
}).listen(3000);
На клиенте это будет выглядеть примерно так:
const source = EventSource('/stream')
sourse.addEventListener('message', (message) => {
console.log(message.data)
})
С таким кодом мы будем получать на клиенте сообщение "ПРИВЕТ!" каждую секунду
При этом, конечно же, никто не мешает усложнить логику со стороны сервера, и пушить новые сообщения в стрим не каждую секунду, а только при изменении данных
И конечно же никто не запрещает обернуть стрим в какой-нибудь React хук и сделать дженеричное решение для всего проекта/проектов
Если вы ни разу не работали SSE, то очень рекомендую потыкать хотя бы в песочнице — очень крутая штука!
Если кратко:
SSE — технология однонаправленной связи от сервера к клиенту
С помощью SSE можно обновлять данные на клиенте в рамках одного соединения в реальном времени
Спасибо за прочтение, это важно для меня
@prog_way_blog — чат — #theory #javascript #code #data #web
Please open Telegram to view this post
VIEW IN TELEGRAM
👍39❤13🔥7🐳3
Как создать массив фиксированной длины?
На самом деле, способов множество. Можно создать простой массив пустых элементов:
Но тогда будет проблема с тем, чтобы его заполнить.
Решить её очень просто — можно просто заполнить массив через метод
Или мы можем попробовать вызвать метод
Пробуйте угадать что получится в ходе выполнения кода выше😂
А что будет, если вызвать вот такой код?
Ответ:ноль, потому что значений в массиве по сути то и нет. Поэтому и не работает
Поэтому если мы хотим использовать то придётся использовать вот такой хак :
Такая конструкция уже превратит разряженный массив в массив из сотни
Мой любимый способ, который я использую всегда в подобных кейсах:
Мне так привычнее и синтаксически наиболее понятно. Да и ещё фишка в том, что вторым аргументом в
Ну или прям совсем в лоб, про такое тоже не забываем:
Спасибо за прочтение, это важно для меня❤️
@prog_way_blog — чат — #web #javascript #theory #data
На самом деле, способов множество. Можно создать простой массив пустых элементов:
Array(100)
Но тогда будет проблема с тем, чтобы его заполнить.
Решить её очень просто — можно просто заполнить массив через метод
fill
:Array(100).fill(0)
Или мы можем попробовать вызвать метод
map
и заполнить массив индексами:Array(100).map((_, index) => index)
Пробуйте угадать что получится в ходе выполнения кода выше
Ответ:⬇️
Получится [empty × 100], а не массив индексов)
Тут дело в том, что при вызове Array(100) у нас изначально создаётся "разряженный" массив. Это когда под каждый элемент массива даже память не выделяется.
Язык просто создаёт пустую структуру с полем length в значении 100
А что будет, если вызвать вот такой код?
Object.keys(Array(100)).length
Ответ:
map
map
, [...Array(100)].map((_, index) => index)
Такая конструкция уже превратит разряженный массив в массив из сотни
undefined
и позволит вызвать map
Мой любимый способ, который я использую всегда в подобных кейсах:
Array.from({ length: 100 })
Мне так привычнее и синтаксически наиболее понятно. Да и ещё фишка в том, что вторым аргументом в
from
можно сразу передать функцию-маппер:Array.from({ length: 100 }, () => 'привет')
Ну или прям совсем в лоб, про такое тоже не забываем:
const array = []
for (let i = 0; i < 100; i++) {
array.push('progway')
}
Спасибо за прочтение, это важно для меня
@prog_way_blog — чат — #web #javascript #theory #data
Please open Telegram to view this post
VIEW IN TELEGRAM
👍35❤8🐳7👀3🤔1🗿1