M3 | WebDev
102 subscribers
105 photos
4 videos
14 links
Your guide to the world of programming 🌐🚀
Download Telegram
🔄 Event Loop в JavaScript — как это работает?

JavaScript — однопоточный язык, но при этом он умеет обрабатывать асинхронные задачи. Как? За счёт Event Loop!

🚀 Как это работает?

1️⃣ Call Stack — выполняет код синхронно. Если функция завершилась, удаляем её из стека.

2️⃣ Web APIs — браузер обрабатывает асинхронные задачи (setTimeout, fetch, DOM события).

3️⃣ Queue — готовые к выполнению колбэки и промисы.

4️⃣ Event Loop — проверяет стек и очереди, двигая выполнение вперёд.

🔥 Что важно знать?

• Microtasks (промисы, MutationObserver) выполняются до рендеринга и перед setTimeout.
• setTimeout(0) не выполняется мгновенно — он ждёт очистки стека.
• async/await — это просто сахар над промисами и всё равно попадает в microtask queue.
🔼 Всплытие событий в JavaScript

Когда ты кликаешь по кнопке внутри div, почему срабатывает не только click на кнопке, но и на div? Это всплытие событий (Event Bubbling)!

🔥 Как это работает?
1️⃣ Событие сначала срабатывает на целевом элементе (куда кликнули).
2️⃣ Затем поднимается вверх по DOM-дереву (родитель, потом его родитель и так до document).

📌 Пример:
document.querySelector("div").addEventListener("click", () => {
console.log("Клик по div!");
});

document.querySelector("button").addEventListener("click", () => {
console.log("Клик по button!");
});


👉 Клик по button выведет:
Клик по button!
Клик по div!


Так как click сначала отработает на кнопке, а затем поднимется к div.

🛑 Как остановить всплытие?
Если не хочешь, чтобы событие всплывало:
event.stopPropagation();


⚡️ Полезно знать:
stopPropagation() остановит всплытие, но не предотвратит сам обработчик.
stopImmediatePropagation() ещё и заблокирует другие обработчики на этом же элементе.
Всплытие можно использовать, например, для делегирования событий (повесить click на общий контейнер вместо множества кнопок).
🔥 Разница между == и === в JavaScript – почему [] == ![] возвращает true?
Если ты уже немного знаком с JavaScript, то знаешь, что == (нестрогое равенство) и === (строгое равенство) работают по-разному.

👉 === сравнивает значение и тип данных:
0 === "0"; // false, потому что number !== string


👉 == выполняет приведение типов (type coercion):
0 == "0"; // true, потому что строка "0" приводится к числу 0


Но почему [] == ![]true? Разбираем пошагово:

1️⃣ ![] – пустой массив [] приводится к true, но оператор ! инвертирует его, превращая в false.
console.log(![]); // false


2️⃣ Теперь сравниваем [] == false.

• JavaScript приводит false к 0 (логическое false → числовой 0).
[] в числовом контексте приводится к "" (пустая строка).
• Пустая строка "" тоже приводится к 0.

Таким образом, [] == "" и "" == 0, а значит:
[] == false // true


📌 Вывод: == может вести себя неожиданно из-за автоматического приведения типов. Чтобы избежать сюрпризов, используй ===, если не хочешь неожиданных багов!
🚀 Hoisting в JavaScript – почему функции можно вызывать до их объявления?

В JavaScript есть интересная особенность – hoisting (поднятие). Благодаря ему мы можем вызывать функции до их объявления:
hello(); // "Привет!"

function hello() {
console.log("Привет!");
}


Как это работает? Давай разберёмся!

🔍 Что такое hoisting?
Hoisting – это механизм JavaScript, при котором объявления переменных и функций «поднимаются» в начало своего контекста перед выполнением кода.

🔥 Hoisting функций
Функции, объявленные через function, полностью поднимаются, поэтому их можно вызывать до определения:
sayHi(); // "Привет!"

function sayHi() {
console.log("Привет!");
}

JavaScript сначала «видит» всё объявление функции, поэтому вызов работает.

⚠️ Hoisting var и let/const
Но с переменными всё не так просто:
console.log(name); // undefined
var name = "Alice";


Переменные, объявленные через var, поднимаются, но без значений (они инициализируются undefined).

А вот let и const ведут себя иначе:
console.log(age); // ReferenceError
let age = 25;

Переменные, объявленные через let и const, поднимаются, но остаются в «мертвой зоне» (TDZ) до момента инициализации, поэтому доступ к ним до объявления вызывает ошибку.

⚠️ Hoisting для function expression
Функции, объявленные через const или let, не поднимаются полностью:
greet(); //  TypeError: greet is not a function

const greet = function() {
console.log("Привет!");
};

Здесь greet ведёт себя как переменная const, а не как function, поэтому в момент вызова она ещё не инициализирована.

📌 Итог
Function Declaration (обычные функции) поднимаются полностью.
⚠️ Function Expression (const func = function() {}) поднимаются как переменные и недоступны до объявления.
⚠️ var поднимается, но остаётся undefined.
let и const поднимаются, но остаются в TDZ и вызывают ошибку при доступе до объявления.

💡 Вывод: Hoisting – мощная особенность JavaScript, но будь внимателен с let, const и function expression, чтобы избежать неожиданных ошибок!
🚀 Разница между function declaration, function expression и arrow function в JavaScript
В JavaScript есть три способа объявления функций, и каждый из них ведёт себя по-разному. Давай разберёмся!

1️⃣ Function Declaration
📌 Объявление функции в коде без присваивания переменной.
function sayHello() {
console.log("Привет!");
}

Можно вызывать до объявления (hoisting работает).
Имеет собственный this, привязанный к объекту вызова.
Может быть переопределена в коде.

2️⃣ Function Expression
📌 Функция, присвоенная переменной.
const sayHello = function() {
console.log("Привет!");
};

⚠️ Нельзя вызывать до объявления – вызовет ошибку
Можно передавать как аргумент в другие функции.
Полезна при передаче в обработчики событий.

3️⃣ Arrow Function (=>)
📌 Короткий синтаксис для функций.
const sayHello = () => {
console.log("Привет!");
};

🔹 Главное отличие – у arrow function нет своего this, она берёт this из внешнего контекста:
const obj = {
name: "Alice",
sayHi: function() {
console.log(this.name); // "Alice"
},
sayHiArrow: () => {
console.log(this.name); // undefined (берёт `this` из глобального контекста)
}
};

obj.sayHi();
obj.sayHiArrow();

Короткий синтаксис (особенно для однострочных функций).
Не создаёт свой this (удобно в обработчиках событий и методах классов).
Нельзя использовать с new – не имеет prototype.
Нет arguments, вместо него используют ...rest.
🔥 Как работает this в JavaScript? – примеры с call, apply, bind

В JavaScript this – это ключевое слово, которое зависит от контекста вызова функции. Давай разберёмся, как оно работает, и как его можно контролировать с помощью call, apply и bind.

🔍 this в разных контекстах
📌 В методе объекта – this указывает на сам объект:
const user = {
name: "Alice",
sayHi() {
console.log(this.name);
}
};

user.sayHi(); // "Alice"


📌 В обычной функции (function) значение this зависит от режима:

Без строгого режима ("use strict"):
В браузере this указывает на window.
В Node.js this будет {} в модуле, а в функции – undefined.
function showThis() {
console.log(this);
}

showThis();
// В браузере без "use strict" → window
// В Node.js → undefined


• В строгом режиме ("use strict") this всегда undefined в обычных функциях:
"use strict";

function showThis() {
console.log(this);
}

showThis();
// undefined


📌 В arrow function this не создаётся, а берётся из внешнего контекста:
const obj = {
name: "Alice",
arrowFunc: () => console.log(this)
};

obj.arrowFunc();
// this возьмётся из глобального контекста (в браузере – window, в Node.js – {})

🚀 Важно: this в arrow function определяется в момент создания и не меняется!

2️⃣ Как изменить this? Используем call, apply, bind
🔥 call – вызов функции с заданным this
Синтаксис: func.call(context, arg1, arg2, ...)
function sayHello() {
console.log(`Привет, ${this.name}`);
}

const user = { name: "Alice" };

sayHello.call(user); // "Привет, Alice"

call сразу вызывает функцию.
Можно передавать аргументы через запятую.

🔥 apply – как call, но аргументы передаются массивом
Синтаксис: func.apply(context, [arg1, arg2, ...])
function sum(a, b) {
console.log(this.value + a + b);
}

const obj = { value: 10 };

sum.apply(obj, [2, 3]); // 10 + 2 + 3 = 15

Удобно, если аргументы уже находятся в массиве.
apply редко используется, так как сейчас есть оператор spread (...):
sum.call(obj, ...[2, 3]); // Тоже самое, что apply


🔥 bind – создаёт новую функцию с привязанным this
Синтаксис: const newFunc = func.bind(context, arg1, arg2, ...)
📌 Пример использования bind в обработчике событий:
const button = {
text: "Нажми меня",
handleClick() {
console.log(this.text);
}
};

document.querySelector("button").addEventListener("click", button.handleClick.bind(button));

Если вызвать handleClick без bind, this станет undefined, так как обработчик событий меняет контекст вызова.

🚀 Итог
Используй call, если нужно вызвать функцию сразу и передать this.
Используй apply, если аргументы уже находятся в массиве.
Используй bind, если нужно привязать this и вызвать функцию позже.
🔥 Зачем нужны Symbol в JavaScript?
В JavaScript есть 7 примитивных типов данных: string, number, boolean, null, undefined, bigint, и… Symbol.

Но зачем нам Symbol, если есть строки? 🤔 Давай разбираться!

1️⃣ Что такое Symbol?
Symbol – это уникальное и неизменяемое значение, которое можно использовать как ключ для свойств объекта.
const id = Symbol("id");
console.log(id); // Symbol(id)

🔹 Symbol("id") – это новый уникальный символ.
🔹 Даже если создать два одинаковых Symbol, они не равны:
const sym1 = Symbol("test");
const sym2 = Symbol("test");

console.log(sym1 === sym2); // false

Это ключевая особенность Symbol – каждый новый Symbol() уникален! 🚀

2️⃣ Symbol как ключ в объекте
В отличие от строк, Symbol не виден при обычном переборе for...in и Object.keys(), что делает его полезным для скрытых свойств:
const user = {
name: "Alice",
[Symbol("id")]: 123
};

console.log(user);
// { name: 'Alice', [Symbol(id)]: 123 }

console.log(Object.keys(user));
// ['name'] – символ не виден!

💡 Используется, когда нужно хранить "скрытые" свойства, которые не мешают основному API объекта.

3️⃣ Глобальные Symbol
Если нам нужно глобально переиспользовать один и тот же Symbol, используем Symbol.for():
const sym1 = Symbol.for("shared");
const sym2 = Symbol.for("shared");

console.log(sym1 === sym2); // true

💡 В отличие от обычных Symbol, Symbol.for("key") всегда создаёт или переиспользует существующий символ.

🔎 Чтобы получить имя Symbol, используем .keyFor():
const sym = Symbol.for("shared");
console.log(Symbol.keyFor(sym)); // "shared"


4️⃣ Symbol в системных свойствах
JavaScript использует Symbol для скрытых механизмов языка, например:

🔹 Symbol.iterator – делает объект итерируемым:
const iterableObj = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => ({
value: this.data[index++],
done: index > this.data.length
})
};
}
};

for (const value of iterableObj) {
console.log(value); // 1, 2, 3
}

🔹 Symbol.toPrimitive – настраивает преобразование объекта в примитив:
const obj = {
value: 100,
[Symbol.toPrimitive](hint) {
return hint === "string" ? "Объект" : this.value;
}
};

console.log(obj + 1); // 101
console.log(String(obj)); // "Объект"


📌 Когда использовать Symbol?
Для уникальных ключей в объектах, которые не конфликтуют с другими свойствами.
Для скрытых свойств, которые не должны быть доступны через Object.keys().
Для работы с системными Symbol, такими как Symbol.iterator.
🔄 Как работают генераторы и yield в JavaScript?
В JavaScript есть обычные функции, которые выполняются сразу и до конца. А есть генераторы – это особый вид функций, которые могут приостанавливать выполнение и продолжать его позже. Давай разбираться!

1️⃣ Что такое генератор?
Генератор – это функция, которая может возвращать несколько значений по очереди.

📌 Для создания генератора используется function* (звёздочка * после function).
📌 Вместо return используется yield, который приостанавливает выполнение функции.
function* myGenerator() {
yield "Первое значение";
yield "Второе значение";
yield "Третье значение";
}

const gen = myGenerator(); // Создаём объект генератора

console.log(gen.next()); // { value: 'Первое значение', done: false }
console.log(gen.next()); // { value: 'Второе значение', done: false }
console.log(gen.next()); // { value: 'Третье значение', done: false }
console.log(gen.next()); // { value: undefined, done: true }

🔹 Каждый вызов gen.next() возвращает объект { value, done }, где:

value – текущее значение из yield,
donetrue, если генератор завершён.

2️⃣ Как yield приостанавливает выполнение?
Генератор не выполняется полностью при вызове – он останавливается на yield и ждёт следующего вызова .next().
function* numbers() {
console.log("Запуск генератора");
yield 1;
console.log("Продолжение генератора");
yield 2;
}

const gen = numbers();

gen.next(); // Запуск генератора, { value: 1, done: false }
gen.next(); // Продолжение генератора, { value: 2, done: false }
gen.next(); // { value: undefined, done: true }

🔹 console.log() срабатывает не сразу, а только при вызове .next()!

3️⃣ Передача данных в yield
Можно передавать значения внутрь генератора с помощью .next(value).
function* sayHello() {
const name = yield "Как тебя зовут?";
yield `Привет, ${name}!`;
}

const gen = sayHello();

console.log(gen.next().value); // "Как тебя зовут?"
console.log(gen.next("Alice").value); // "Привет, Alice!"

🔹 Первый .next() просто начинает выполнение и доходит до первого yield.
🔹 Второй .next("Alice") передаёт "Alice" внутрь генератора вместо yield.

4️⃣ yield* – делегирование другому генератору
Если нужно вызвать другой генератор внутри текущего, можно использовать yield*:
function* firstGen() {
yield "A";
yield "B";
}

function* secondGen() {
yield "1";
yield* firstGen();
yield "2";
}

const gen = secondGen();

console.log([...gen]); // ["1", "A", "B", "2"]

🔹 yield* делегирует выполнение другому генератору.

5️⃣ Бесконечные генераторы
Генераторы можно использовать для создания бесконечных последовательностей:
function* infiniteCounter() {
let i = 1;
while (true) {
yield i++;
}
}

const counter = infiniteCounter();

console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
console.log(counter.next().value); // 3

🔹 В отличие от обычных циклов, бесконечный генератор не блокирует выполнение кода, потому что yield останавливает его до следующего вызова .next().

📌 Итоги
Генераторы (function*) позволяют останавливать и продолжать выполнение функций.
yield возвращает значения и приостанавливает выполнение до следующего .next().
Можно передавать данные в yield через .next(value).
yield* делегирует выполнение другому генератору.
Генераторы подходят для работы с потоками данных, итераторов и бесконечных последовательностей.
🔥 Прототипное наследование в JavaScript – что такое __proto__ и Object.create()?

JavaScript поддерживает объектно-ориентированное программирование (ООП), но его модель основана не на классах в привычном виде, а на прототипном наследовании.

Давай разберёмся, как это работает и какую роль в этом играют __proto__ и Object.create()! 🚀

1️⃣ Как работает прототипное наследование?
📌 В JavaScript каждый объект может наследовать свойства и методы от другого объекта через прототип.

Принцип работы:
• Если свойство ищется в объекте и не найдено, JavaScript ищет его в прототипе (в __proto__).
• Если оно не найдено и там, поиск идёт дальше по цепочке прототипов.
const parent = {
greet() {
console.log("Привет от родителя!");
}
};

const child = Object.create(parent); // Указываем, что parent – прототип

child.greet(); // "Привет от родителя!" (взято из прототипа)

📌 Здесь child не имеет метода greet(), но он нашёл его в parent через прототип!

2️⃣ Что такое __proto__?
🔹 __proto__ – это ссылка на прототип объекта.
const obj = { a: 10 };
const newObj = Object.create(obj);

console.log(newObj.__proto__ === obj); // true
console.log(newObj.a); // 10 (взято из прототипа)

⚠️ Не путай __proto__ и prototype!

__proto__ – это свойство объекта, которое указывает на его прототип.
prototype – это свойство функции-конструктора, которое будет прототипом всех её экземпляров.
function User(name) {
this.name = name;
}

User.prototype.sayHi = function() {
console.log(`Привет, ${this.name}!`);
};

const user = new User("Alice");

console.log(user.__proto__ === User.prototype); // true
user.sayHi(); // "Привет, Alice!"

__proto__ лучше не использовать напрямую, так как он медленный. Вместо него – Object.create() или Object.setPrototypeOf().

3️⃣ Object.create() – лучший способ наследования
Вместо того чтобы менять __proto__, лучше использовать Object.create(proto), который создаёт новый объект с указанным прототипом:
const animal = { sound: "Meow" };
const cat = Object.create(animal);

console.log(cat.sound); // "Meow" (взято из прототипа)

📌 Object.create() создаёт объект без лишних свойств и позволяет точно контролировать прототип.

4️⃣ Цепочка прототипов (prototype chain)
Прототипное наследование образует цепочку (prototype chain):
const grandparent = { grandparentProp: "👴" };
const parent = Object.create(grandparent);
parent.parentProp = "👨";

const child = Object.create(parent);
child.childProp = "👶";

console.log(child.childProp); // "👶" (из самого объекта)
console.log(child.parentProp); // "👨" (из прототипа)
console.log(child.grandparentProp); // "👴" (из прототипа прототипа)
console.log(child.toString()); // `[object Object]` (из Object.prototype)

📌 В конце цепочки всегда стоит Object.prototype, который имеет методы toString(), hasOwnProperty() и т.д.

5️⃣ class – удобный синтаксис для прототипного наследования
В ES6 появился class, который не заменяет прототипное наследование, а делает его удобнее:
class Animal {
constructor(name) {
this.name = name;
}

speak() {
console.log(`${this.name} издаёт звук.`);
}
}

const cat = new Animal("Мурзик");
cat.speak(); // "Мурзик издаёт звук."

📌 Под капотом class всё равно работает через прототипы:
console.log(Animal.prototype.speak); // Функция в прототипе класса
console.log(cat.__proto__ === Animal.prototype); // true


6️⃣ Наследование через extends
Классы позволяют удобно наследовать поведение с помощью extends:
class Dog extends Animal {
speak() {
console.log(`${this.name} лает!`);
}
}

const rex = new Dog("Рекс");
rex.speak(); // "Рекс лает!"

📌 Под капотом extends работает так же, как Object.create().

🚀 Вывод
• Прототипное наследование – основа JavaScript.
class – это просто удобная оболочка над прототипами.
• Используй Object.create() для лёгкого наследования.
Разница между isNaN и Number.isNaN заключается в их поведении при проверке значений, которые нельзя преобразовать в число.

🔹 isNaN(value)
• Преобразует переданное значение в число, а затем проверяет, является ли оно NaN.
• Из-за приведения типов isNaN('abc') сначала пытается преобразовать строку 'abc' в число, получает NaN, а затем проверяет isNaN(NaN), что дает true.

🔹 Number.isNaN(value)
• Проверяет только те значения, которые уже являются NaN без преобразования типов.
'abc' не является NaN, а просто строка, поэтому Number.isNaN('abc') возвращает false.

🔥 Разница в работе:
console.log(isNaN('abc')); // true  (так как 'abc' → NaN)
console.log(Number.isNaN('abc')); // false (так как 'abc' не является NaN)


📌 Вывод:
isNaN менее строгий и преобразует входные данные.
Number.isNaN более строгий и проверяет только NaN без преобразования.
9 законов, которые избавят тебя от боли в коде и хаоса в команде

Почему стоит их знать?

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

1️⃣ Закон Парето
20% усилий дают 80% результата. Сосредоточься на главном.

2️⃣ Закон Гудхарта
Когда метрики становятся целью, они теряют смысл. KPI ≠ продуктивность.

3️⃣ Закон Конвея
Архитектура повторяет структуру команды. Бардак в команде = бардак в коде.

4️⃣ Закон Питера
Топовый дев ≠ топовый тимлид. Хард- и софт-скиллы — разные вселенные.

5️⃣ Закон Брукса
Больше разработчиков ≠ быстрее. Координация съедает время.

6️⃣ Закон Хайрама
Если у API много пользователей — кто-то точно начнёт использовать баг как фичу. Потом не отвяжешься.

7️⃣ Закон Линуса
Чем больше глаз смотрит на код — тем быстрее найдутся баги. Спасибо, опенсорс.

8️⃣ Закон Кернигана
Сложный код — отложенная боль. Пиши просто, не геройствуй.

9️⃣ Закон Хофтшадтера
Даже если думаешь, что учёл задержки — не учёл. Всегда добавляй буфер.
Использование as в TypeScript — это type assertion (уточнение типа). Хотя это мощный инструмент, у него есть несколько минусов и подводных камней, особенно при неправильном использовании:

1. Игнорирование реального типа
const value = "hello" as number;

➡️ TypeScript "поверит" вам, что это number, но на самом деле это строка. Это может привести к ошибкам во время выполнения, особенно если вы используете тип не по назначению.

2. Скрытие ошибок
const user = getUser() as AdminUser;

➡️ Даже если getUser() возвращает GuestUser, компилятор не проверит это, и вы потеряете типовую безопасность.

3. Сложности с рефакторингом
Если вы используете as повсеместно, IDE и инструменты анализа кода теряют возможность правильно находить ошибки типов при изменениях в коде.

4. "Двойное" приведение типа — потенциально опасно
const input = "123" as unknown as number;

➡️ Часто используется для обхода ограничений компилятора, но абсолютно небезопасно. Это способ сказать TypeScript: "я знаю, что делаю", но чаще всего — вы не знаете.

5. Труднее читать и поддерживать
При большом количестве as в коде:
• Сложнее понять, откуда и почему у значения появился тот или иной тип.
• Новому разработчику трудно доверять таким местам в коде.

Когда as допустим:
• Приведение DOM-элементов:
const input = document.querySelector('input') as HTMLInputElement;

• Ситуации, когда TypeScript не может вывести тип, а вы уверены в структуре данных.
• Работа с unknown:
const data = JSON.parse(json) as MyType;


🧠 Альтернатива: Type Guards
Вместо as, по возможности, используйте type guards:
function isAdmin(user: User): user is Admin {
return (user as Admin).role === 'admin';
}


Использование as должно быть осознанным исключением, а не повседневной практикой.
удалил(-а) Вас из группы