JavaScript

Common
Complex types of data
Map - ассоциативный массив / словарь
Это объект, где ключом может помимо строки быть любой тип данных
Пример оптимизации Мапом
// было const getAssets = async () => [ //pseudo-GET req { symbol: "APPL", price: 100 }, { symbol: "GOOG", price: 50 }, { symbol: "MSFT", price: 25 }, ]; const resp = await getAssets(); // iterates every time in Biseness Logic Layer resp.find((asset) => asset.symbol === "MSFT"); // стало const transformAssetsArrayToMap = async () => { const respArray = await getAssets(); const respMap = new Map(); // iterates 1 time in Data Acess Layer respArray.forEach((asset) => { const { symbol, ...rest } = asset; respMap.set(symbol, { ...rest }); }); return respMap; }; const resp = await transformAssetsArrayToMap(); // instant in BLL resp.get("MSFT");
Set - массив, только с уникальными значениями
Можно отфильтровать дубликаты с массива
const uniqArr = [...new Set(arr)];
WeakMap - словарь со слабыми ссылками
Нужен для того, чтобы помочь garbage collector удалить неиспользуемые объекты т.к. в Map они хранятся даже когда все ссылки в коде на них были перезаписаны
Ключом могут быть только объекты. Не поддерживает методы, которыми можно взять все ключи сразу (keys(), values(), entries() т.к. мы не знаем в какой момент происходит удаление объектов сборщиком мусора)
Напр. Map
let john = { name: "John" }; let map = new Map(); map.set(john, "..."); john = null; // overwrite the reference // объект john сохранён внутри объекта `Map` // и будет занимать место в памяти, хотя уже не используется в коде, // и доступен только через map.keys()
Same code with WeakMap:
let john = { name: "John" }; let weakMap = new WeakMap(); weakMap.set(john, "..."); john = null; // перезаписываем ссылку на объект // объект john !будет удалён из памяти сборщиком!
memoization example with WeakMap
WeakSet
Тоже самое, только Set-подобная структура
тоже ключи только объекты и тоже удаляет из памяти неиспользуемые в иных местах объекты
Hoisting - поднятие
При поднятии (hoisting) декларации var переменных поднимаются и инициализируются значением undefined, а объявления функций поднимаются вместе со своей реализацией
do hoist:
function declaration(){}var
don't hoist:
let functionExpression = () => {}let functionExpression = function (){}let/const
It's correct code;
a = 1; var a; // compilator will hoist this line upper
Boxing/Unboxing
Вызывая .length у строки, движок js оборачивает примитив "str" в new String("str")
и у этого объекта вызывает поле .length,
после чего удаляет объект, чтобы мы не могли присвоить ему своих полей.
Objects
Object creating:
//inherite user object as prototype const admin = Object.create(user); let admin = { __proto__: user }; let admin = new Object({ __proto__: user });
Object copying:
Reference copying
let admin = user; ({}) === {}; // false, objects are never equal, only references
Shallow copying
- Object.assign
let user = { name: "John", age: 30, }; let anotherObject = { role: "admin" }; let shallowClone = Object.assign({}, user, anotherObject);
- Spread operator
let shallowClone = { ...user, ...anotherObject };
Deep copying
- JSON.parse(JSON.stringify(originalObj))
const originalObj = { a: 1, b: { c: 2 } }; const clone = JSON.parse(JSON.stringify(originalObj));
!!!JSON format doesn't support methods, so you can't copy methods in such way.It's also expensive in computational costs
Recursive copying implementation
lodash.deepClone
window.structuredClone(modern browsers only)
!!!structuredClone can't copy methods
Functions
Замыкание closure
Когда функция запоминает своё лексическое окружение в скрытом свойстве [[Environment]]
Напр. дочерняя функция запомнит параметр родительской функции
const maxLengthCreator = (maxLength) => { return (value) => { return value.length > maxLength ? `Max length is ${maxLength} symbols` : undefined; }; }; const max10 = maxLengthCreator(10); //max10 функция замкнула значение maxLength == 10 max10("This is string more than 10 letters");
Псевдомассив arguments
-
даже если мы объявили функцию без аргументов, мы всё равно можем передать в неё параметры, они попадут в специальный псевдомассив(нет методов массива) и к ним можно будет обратиться по индексам –
-
В
use strictarguments ведет себя по другому, с пересваиванием параметров -
У стрелочных нету псевдомассива arguments
Современная замена - …rest оператор
function(first, second, …restArgs){} – остальные аргументы сложаться в настоящий массив

Asynchrony
Event loop
Ответ на заучить:
- Компилятор в колл стеке выполняет синхронный код
- И регистрирует асинхронные задачи в одной из очередей
- И попутно при необходимости выполняя web API (!!! Напр. 10 сек таймера или обработчик события будет висеть и его ждать)
- Колл стек свободен - Далее выполняются асинхронные задачи
- начиная с очереди микрозадач, выполняются ВСЕ микрозадачи в очереди
- начиная с самой старой
- ТУТ РЕРЕНДЕР
- начиная с очереди микрозадач, выполняются ВСЕ микрозадачи в очереди
- Колл стек и очередь микро свободна - Далее выполняется ОДНА самая старая макрозадача
- после этого цикл повторяется
Theory:
-
Call Stack - Это синхронный код.
- стэк, стопка - выполняются сначала новые
-
Callback (Microtask) queue - После освобождения стека в очереди выполняются микрозадачи
- микрозадачи могут возвращать другие микрозадачи, создавая очередь микрозадач - chain of
.then&.catch) - очередь - выполняются сначала старые
- помимо промисов есть
queueMicrotask(Web) &process.nextTick(Node)
- микрозадачи могут возвращать другие микрозадачи, создавая очередь микрозадач - chain of
-
Macro(task) queue - После освобождения стека и очереди микрозадач, event loop проверяет наличие макрозадач. Самая старая макрозадача помещается в стек, выполняется и затем выполняются все новые микрозадачи, добавленные в очередь. Далее выполняется следующая макрозадача и т.д.
- Это Web APIs (fetch, setTimeout, слушатели событий).

Common cases:
Regular: Sync code > Async code (micro - high prio) > Browser API code (macro - low prio)
Macro in Micro (SetTimeout in Promise): sync code > setTimeout > .then
const promise = new Promise((resolve, reject) => { console.log(1); setTimeout(() => { console.log("timerStart"); resolve("success"); console.log("timerEnd"); }, 0); console.log(2); }); promise.then((res) => { console.log(res); });
Micro in Macro (Promise in SetTimeout): sync code > micro > macro
const timer1 = setTimeout(() => { console.log("timer1"); const promise1 = Promise.resolve().then(() => { console.log("promise1"); }); }, 0); const timer2 = setTimeout(() => { console.log("timer2"); }, 0);
Example:
function logA() { console.log("A"); } function logB() { console.log("B"); } function logC() { console.log("C"); } function logD() { console.log("D"); } function logE() { console.log("E"); } function timeoutWithPromise() { Promise.resolve().then(logE); } setTimeout(timeoutWithPromise, 0); // 5 logA(); // 1 setTimeout(logB, 5000); // 6 Promise.resolve().then(logC).then(logA); // 3 - 4 logD(); // 2
Step 1. Evaluate Script
- logA > console.log(A) // call stack
- logB // task queue
- logC // microtask queue
- logD > console.log(D) // call stack
Step 2. Run all Microtasks
- logC// from micro to call stack > > console.log(C) // call stack
- logA // microtask queue (.then chain)
- logA// from micro to call stack > > console.log(A) // call stack
Step 3. Rerender
Step 4. Run a Task
- timeoutWithPromise // call stack
Step 2. Run all Microtasks
- log E // microtask queue
- log E // from micro to call stack >> > console.log(E) // call stack
- log F // microtask queue 12 log F // from micro to call stack >> console.log(F) // call stack
Step 3. Rerender
Step 4. Run a Task
- logB > console.log(B) // call stack
Promise
Создание - конструктор new Promise с аргументом в виде cb, в которую передаются аргументами передаются функции resolve & reject.
const myPromise = new Promise((resolve, reject) => {});
Cостояние - pending | fulfilled | rejected. Хранится в скрытом свойстве экземпляра [[PromiseState]]
Подписка - у экземпляра есть функции then, catch, finally для подписки на изменения состояния промиса
Результат - хранится в скрытом свойстве экземпляра [[PromiseResult]]. Передаётся в функции подписки:
.then(data => { })
.catch(error => { })
Статические методы:
all:
- возвращает другой promise, который зарезолвится когда ЗАРЕЗОЛВЯТСЯ ВСЕ промисы из его массива.
- если хотя бы один из [] rejected, то promise из all тоже rejected
- .then all сидит массив результатов всех промисов
race:
- отработает, когда первый promise из массива выполнится
allSettled:
- возвращает promise, который зарезолвится, когда все промисы из его массива уйдут из состояния pending (попадем в then, даже если некоторые зареджектятся)
Chains:
каждый then и catch возвращают новый промис. Catch как и then может вернуть fulfilled или rejected промис.
.then(data => Promise.reject() ).then().then() - 2 и 3 промисы не будут испольнены
.then(data => Promise.resolve() ).catch().then() - catch не будет исполнен
.then(data => Promise.reject() ).catch().then() - все функции-подписчики будут исполнены
Browser Javascript
Terms
removeEventListener("click", название функции) -- нам нужно назвать функцию внутри, если мы хотим удалить
event.target - элемент, на котором событие возникло изначально
event.currentTarget - элемент, на котором обработчик события был назначен
Фазы события: Погружение -- Цель - Всплытие
Всплытие (bubbling)
Когда кликаем на button внутри div, событие сработает сначала в button потом в div потом в body
Остановить можно event.stopPropagation в addEventListener
event.stopImmediatePropagation останавливает всплытие даже на соседние элементы (в одном родителе)
Погружение (immersion)
element.addEventListener( "click", (e) => { e.preventDefault(); }, { capture: true }, );
3-ий аргумент опциональный(либо просто true). Полезно, если нужно выполнить событие сначала на родительском элементе
Делегирование событий(event delegation):
Пример: допустим у нас 800к комментов и на каждый придется вешать свой обработчик события для лайка/дизлайка.
Вместо этого используется прием делегирования -
вешаем обработчик на родителя элементов и с помощью if (event.target.tagName) указываем где именно должен произойти клик, таким образом мы сэкономили кучу памяти.
FP - Functional Programming
КАРРИРОВАНИЕ (carrying)
maxLengthCreator(10)("This is string from some input");
Это трансформация функций таким образом, чтобы они принимали аргументы не как fun(a, b, c), а как fun(a)(b)(c).
const sum = (n) => (b) => b + n; sum(1)(2); // 3
sum is a HOF (High order function)
similar conception exists in React - HOC (High order component)
Popular tasks
Мемоизация (кэширование)
const add = (a, b) => a + b; // HOF-мемоизатор function memoize(func) { const cache = {}; return function (...args) { // Формируем уникальный ключ для комбинации аргументов let keyForThisCall = JSON.stringify(args); if (cache[keyForThisCall]) { // Если результат уже в памяти return cache[keyForThisCall]; // возвращаем его } else { const result = func(args[0], args[1]); // Иначе считаем cache[keyForThisCall] = result; // и сохраняем в память return result; } }; } const memoizedAdd = memoize(add); console.log(memoizedAdd(2, 3)); // Складываем 2 и 3, результат 5 console.log(memoizedAdd(2, 3)); // Берем из памяти 2,3, результат 5
Advanced, мемоизация с WeakMap/WeakSet
const difficultCalculationsWithObject = (obj) => { return { ...obj }; }; // HOF-мемоизатор function memoize(func) { const cache = new WeakMap(); return function (...args) { // Уникальным ключом будет ссылка на объект переданный в аргументах const keyForThisCall = args[0]; if (cache.has(keyForThisCall)) { const cachedResult = cache.get(keyForThisCall); // Если результат уже в памяти return cachedResult; // возвращаем его } else { let result = func(keyForThisCall); // Иначе считаем cache.set(keyForThisCall, result); // и сохраняем в память return result; } }; } const memoizedFunc = memoize(difficultCalculationsWithObject); let obj1 = { a: 1, b: 2 }; console.log(memoizedFunc(obj1)); // calculates 1st call console.log(memoizedFunc(obj1)); // takes from cache 2nd obj1 = {}; // overwrite the reference // позже { a: 1, b: 2 } будет удалён из WeakMap cache и из памяти