حلقة while و do-while في JavaScript (Node.js): دليل مفصل
Объект — это способ объединить связанные данные и действия под одним именем. Переменная хранит одно значение, массив хранит список значений, объект хранит набор именованных значений (и даже функций). В этом уроке вы освоите объекты, познакомитесь с загадочным this, разберёте классы ES6 и поймёте основы объектно-ориентированного программирования.
Что такое объект? Аналогия с паспортом
Представьте паспорт человека. В нём несколько полей: имя, фамилия, дата рождения, гражданство, номер. Все эти данные относятся к одному человеку и хранятся вместе.
Объект в JavaScript — это такой "паспорт" в памяти программы. Набор пар "ключ: значение", где каждый ключ — это название поля, а значение — сами данные.
// Без объекта — разрозненные переменные:
let имя = "Аня";
let возраст = 20;
let город = "Москва";
// Эти данные никак не связаны между собой!
// С объектом — всё вместе:
let пользователь = {
имя: "Аня",
возраст: 20,
город: "Москва"
};
// Теперь ясно, что это данные одного человека
Создание объекта: литеральный синтаксис
// Пустой объект:
let пустой = {};
// Объект с данными:
let кот = {
имя: "Барсик", // строка
возраст: 3, // число
вакцинирован: true, // boolean
хозяйка: null, // null (пока нет хозяйки)
цвет: "рыжий"
};
// Значением может быть любой тип, включая массив и другой объект:
let студент = {
имя: "Петя",
оценки: [85, 90, 78, 92], // массив
адрес: { // вложенный объект
город: "Самара",
улица: "Ленина"
}
};
console.log(студент.оценки[0]); // 85
console.log(студент.адрес.город); // Самара
Доступ к свойствам: точка и скобки
let машина = {
марка: "Toyota",
модель: "Camry",
год: 2022,
цвет: "белый"
};
// Через точку (рекомендуется когда имя известно заранее):
console.log(машина.марка); // "Toyota"
console.log(машина.год); // 2022
// Через квадратные скобки (нужно когда имя в переменной):
let свойство = "модель";
console.log(машина[свойство]); // "Camry" — значение переменной используется как ключ
// Квадратные скобки нужны также если ключ содержит пробел или спецсимволы:
let странный = {
"моё-свойство": 42,
"с пробелом": "значение"
};
console.log(странный["моё-свойство"]); // 42
// странный.моё-свойство — ошибка!
// Несуществующее свойство:
console.log(машина.мощность); // undefined (не ошибка, просто нет такого свойства)
// Проверить наличие свойства:
console.log("марка" in машина); // true
console.log("мощность" in машина); // false
Изменение и добавление свойств
let человек = {
имя: "Боря",
возраст: 25
};
// Изменить существующее свойство:
человек.возраст = 26;
console.log(человек.возраст); // 26
// Добавить новое свойство:
человек.город = "Казань";
человек.хобби = ["футбол", "программирование"];
console.log(человек); // { имя: 'Боря', возраст: 26, город: 'Казань', хобби: [...] }
// Удалить свойство:
delete человек.город;
console.log(человек.город); // undefined
Методы объекта
Объекты могут хранить не только данные, но и функции. Функция внутри объекта называется методом. Именно так работают методы массивов, которые мы изучили — push, filter, map — это методы объекта-массива.
let собака = {
имя: "Шарик",
порода: "Дворняга",
возраст: 4,
// Метод объекта — функция как свойство:
гавкнуть: function() {
console.log("Гав-гав!");
},
// Краткая запись метода (ES6):
побегать() {
console.log(`${this.имя} бежит!`);
},
// Стрелочная — ОСТОРОЖНО с this (см. ниже):
инфо: function() {
return `${this.имя}, ${this.порода}, ${this.возраст} лет`;
}
};
собака.гавкнуть(); // Гав-гав!
собака.побегать(); // Шарик бежит!
console.log(собака.инфо()); // Шарик, Дворняга, 4 лет
Ключевое слово this
this — одна из самых часто непонимаемых вещей в JavaScript. Но давайте разберём просто.
this внутри метода объекта — это ссылка на сам объект. Аналогия: когда вы говорите "мои руки" — слово "мои" — это this. Оно указывает на вас самих.
let кот = {
имя: "Мурзик",
сказать: function() {
// this здесь = кот (объект, чей метод вызывается)
console.log(`Меня зовут ${this.имя}`);
}
};
кот.сказать(); // Меня зовут Мурзик
// ВАЖНО: стрелочные функции НЕ имеют своего this:
let кот2 = {
имя: "Барсик",
сказать: () => {
// this здесь НЕ = кот2, а унаследован от внешнего контекста!
console.log(`Меня зовут ${this.имя}`); // undefined или ошибка!
}
};
// Поэтому методы объектов пишут НЕ через стрелочные функции
// this при разных вызовах:
function чтоЯ() {
console.log(this);
}
let объект1 = { метод: чтоЯ };
let объект2 = { метод: чтоЯ };
объект1.метод(); // this = объект1
объект2.метод(); // this = объект2
чтоЯ(); // this = undefined (в строгом режиме) или window (в браузере)
Перебор свойств объекта
let персонаж = {
имя: "Гарри",
фамилия: "Поттер",
возраст: 17,
школа: "Хогвартс"
};
// for...in — перебрать все ключи:
for (let ключ in персонаж) {
console.log(`${ключ}: ${персонаж[ключ]}`);
}
// имя: Гарри
// фамилия: Поттер
// возраст: 17
// школа: Хогвартс
// Object.keys — массив ключей:
console.log(Object.keys(персонаж)); // ["имя", "фамилия", "возраст", "школа"]
// Object.values — массив значений:
console.log(Object.values(персонаж)); // ["Гарри", "Поттер", 17, "Хогвартс"]
// Object.entries — массив пар [ключ, значение]:
console.log(Object.entries(персонаж));
// [["имя", "Гарри"], ["фамилия", "Поттер"], ...]
// Удобно с деструктуризацией:
Object.entries(персонаж).forEach(([ключ, значение]) => {
console.log(`${ключ} = ${значение}`);
});
Деструктуризация объектов
Деструктуризация — это быстрое "распаковывание" свойств объекта в переменные:
let пользователь = {
имя: "Катя",
возраст: 22,
город: "Питер",
профессия: "Дизайнер"
};
// Без деструктуризации:
let имя = пользователь.имя;
let возраст = пользователь.возраст;
// С деструктуризацией:
let { имя, возраст } = пользователь;
console.log(имя); // "Катя"
console.log(возраст); // 22
// Переименование при деструктуризации:
let { имя: полноеИмя, город: откуда } = пользователь;
console.log(полноеИмя); // "Катя"
console.log(откуда); // "Питер"
// Значения по умолчанию:
let { имя: им, зарплата = 0 } = пользователь;
console.log(зарплата); // 0 (свойства нет — используется дефолт)
// Деструктуризация в параметрах функции:
function показатьПользователя({ имя, возраст, город = "неизвестно" }) {
console.log(`${имя}, ${возраст} лет, ${город}`);
}
показатьПользователя(пользователь); // "Катя, 22 лет, Питер"
Копирование объектов
let оригинал = { а: 1, б: 2 };
// НЕПРАВИЛЬНО — копирует ссылку, а не данные:
let плохаяКопия = оригинал;
плохаяКопия.а = 100;
console.log(оригинал.а); // 100 — оригинал тоже изменился!
// ПРАВИЛЬНО — поверхностная копия через spread:
let хорошаяКопия = { ...оригинал };
хорошаяКопия.а = 100;
console.log(оригинал.а); // 1 — оригинал не изменился!
// Object.assign — тоже поверхностная копия:
let ещёОдна = Object.assign({}, оригинал);
// Объединить несколько объектов:
let доп = { в: 3, г: 4 };
let полный = { ...оригинал, ...доп };
console.log(полный); // { а: 1, б: 2, в: 3, г: 4 }
Классы ES6: аналогия чертёж → дом
Когда нужно создать много похожих объектов — используют классы. Аналогия идеальная: класс — это чертёж (blueprint), объект — конкретный дом, построенный по этому чертежу. Один чертёж — много домов.
// Класс — это шаблон для создания объектов:
class Животное {
// constructor — вызывается при создании объекта:
constructor(имя, вид, возраст) {
this.имя = имя; // Записываем в свойства объекта
this.вид = вид;
this.возраст = возраст;
}
// Метод класса:
описать() {
return `${this.имя} — ${this.вид}, ${this.возраст} лет`;
}
говорить() {
console.log(`${this.имя} издаёт звук...`);
}
}
// Создаём объекты (экземпляры) класса через new:
let кот = new Животное("Барсик", "кот", 3);
let пёс = new Животное("Шарик", "собака", 5);
let попугай = new Животное("Кеша", "попугай", 10);
console.log(кот.описать()); // Барсик — кот, 3 лет
console.log(пёс.описать()); // Шарик — собака, 5 лет
кот.говорить(); // Барсик издаёт звук...
// Все объекты разные, но созданы по одному шаблону:
console.log(кот instanceof Животное); // true
console.log(пёс instanceof Животное); // true
Наследование: super
Наследование позволяет создать класс на основе другого, добавив или изменив поведение. Аналогия: класс "Электромобиль" наследует всё от класса "Автомобиль", но добавляет батарею и зарядку.
// Базовый класс:
class Животное {
constructor(имя, возраст) {
this.имя = имя;
this.возраст = возраст;
}
говорить() {
console.log(`${this.имя} издаёт звук`);
}
описать() {
return `${this.имя}, ${this.возраст} лет`;
}
}
// Дочерний класс — extends означает "наследуется от":
class Кот extends Животное {
constructor(имя, возраст, окрас) {
super(имя, возраст); // Вызвать конструктор родителя
this.окрас = окрас; // Добавить своё свойство
}
// Переопределить метод родителя:
говорить() {
console.log(`${this.имя} говорит: Мяу!`);
}
// Добавить новый метод:
мурчать() {
console.log(`${this.имя} мурчит: Пррр...`);
}
}
class Собака extends Животное {
говорить() {
console.log(`${this.имя} говорит: Гав!`);
}
}
let барсик = new Кот("Барсик", 3, "рыжий");
let шарик = new Собака("Шарик", 5);
барсик.говорить(); // Барсик говорит: Мяу!
шарик.говорить(); // Шарик говорит: Гав!
барсик.мурчать(); // Барсик мурчит: Пррр...
// Наследует методы родителя:
console.log(барсик.описать()); // Барсик, 3 лет
// Проверка цепочки наследования:
console.log(барсик instanceof Кот); // true
console.log(барсик instanceof Животное); // true
console.log(шарик instanceof Кот); // false
Геттеры и сеттеры
class Температура {
constructor(цельсий) {
this._цельсий = цельсий; // _ означает "внутреннее" свойство
}
// Геттер — вычисляемое свойство (читается как обычное свойство):
get фаренгейт() {
return this._цельсий * 9/5 + 32;
}
// Сеттер — контроль при присвоении:
set цельсий(значение) {
if (значение < -273.15) {
throw new Error("Температура не может быть ниже абсолютного нуля!");
}
this._цельсий = значение;
}
get цельсий() {
return this._цельсий;
}
}
let темп = new Температура(100);
console.log(темп.цельсий); // 100
console.log(темп.фаренгейт); // 212 (вызывается как свойство, не метод!)
темп.цельсий = 37;
console.log(темп.фаренгейт); // 98.6
Практический пример: система пользователей
class Пользователь {
static количество = 0; // Статическое свойство — одно на весь класс
constructor(имя, email, роль = "user") {
this.имя = имя;
this.email = email;
this.роль = роль;
this.дата = new Date().toLocaleDateString("ru");
Пользователь.количество++;
}
статичноеПриветствие() {
return `Привет, ${this.имя}! Вы вошли как ${this.роль}.`;
}
toJSON() {
return { имя: this.имя, email: this.email, роль: this.роль };
}
}
class Администратор extends Пользователь {
constructor(имя, email) {
super(имя, email, "admin");
this.права = ["читать", "писать", "удалять"];
}
удалить(что) {
console.log(`Администратор ${this.имя} удалил: ${что}`);
}
}
let юзер = new Пользователь("Коля", "kolya@mail.ru");
let админ = new Администратор("Боря", "borya@mail.ru");
console.log(юзер.статичноеПриветствие()); // Привет, Коля! Вы вошли как user.
console.log(админ.статичноеПриветствие()); // Привет, Боря! Вы вошли как admin.
админ.удалить("спам");
console.log(`Всего пользователей: ${Пользователь.количество}`); // 2
Типичные ошибки новичков
- Копирование объекта через =.
let б = ане копирует объект — оба указывают на один. Используйтеlet б = { ...а }. - this в стрелочных функциях. Стрелочные функции не имеют своего this. Методы объектов пишите через обычную функцию.
- Забыть new при создании объекта из класса.
let кот = Животное("Барсик")вызовет ошибку. Нужно:new Животное("Барсик"). - Не вызвать super() в дочернем конструкторе. В классе-наследнике всегда нужно вызвать
super()в конструкторе до обращения к this. - Изменение вложенных объектов при shallow copy. Spread делает поверхностную копию. Вложенные объекты всё равно будут общими.
Итого
В этом уроке мы узнали:
- Объект — набор пар "ключ: значение". Хранит связанные данные вместе.
- Доступ через точку (
объект.ключ) или через скобки (объект["ключ"]). - Методы — функции внутри объекта.
- this — ключевое слово, указывающее на объект, чей метод вызывается.
- Деструктуризация — быстрое извлечение свойств:
let { имя, возраст } = объект. - Классы ES6 — шаблоны для создания множества похожих объектов.
- Наследование через extends и super() — создание специализированных классов.
В следующем уроке мы изучим асинхронный JavaScript — одну из важнейших и уникальных особенностей языка.