⚡ JavaScript

Voici la traduction de l'article en français, en respectant toutes les règles :

Объект — это способ объединить связанные данные и действия под одним именем. Переменная хранит одно значение, массив хранит список значений, объект хранит набор именованных значений (и даже функций). В этом уроке вы освоите объекты, познакомитесь с загадочным 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 делает поверхностную копию. Вложенные объекты всё равно будут общими.

Итого

В этом уроке мы узнали:

  1. Объект — набор пар "ключ: значение". Хранит связанные данные вместе.
  2. Доступ через точку (объект.ключ) или через скобки (объект["ключ"]).
  3. Методы — функции внутри объекта.
  4. this — ключевое слово, указывающее на объект, чей метод вызывается.
  5. Деструктуризация — быстрое извлечение свойств: let { имя, возраст } = объект.
  6. Классы ES6 — шаблоны для создания множества похожих объектов.
  7. Наследование через extends и super() — создание специализированных классов.

В следующем уроке мы изучим асинхронный JavaScript — одну из важнейших и уникальных особенностей языка.