Прототипи в JavaScript: Пояснення на Пальцях (і для Співбесіди!)

Привіт! Якщо ви готуєтеся до співбесіди з JavaScript або просто хочете краще зрозуміти, як працює ця мова під капотом, то тема прототипів — це те, що вам точно потрібно знати. Звучить страшно? Не хвилюйтеся, зараз ми все розкладемо по поличках!

Що таке Прототип? Уявіть Собі Трафарет!

Уявіть, що ви хочете намалювати багато однакових котиків. Замість того, щоб щоразу малювати котика з нуля, ви можете зробити трафарет (або шаблон). За допомогою цього трафарету ви швидко створюєте багато схожих котиків.

В JavaScript прототип — це щось схоже на такий трафарет для об’єктів. Це “об’єкт-зразок”, від якого інші об’єкти можуть “успадковувати” властивості та методи (тобто, дії, які вони можуть виконувати).

Чому Це Важливо? Економія Пам’яті та Успадкування

Кожен об’єкт в JavaScript має приховане посилання на свій прототип. Коли ви намагаєтеся отримати доступ до якоїсь властивості або викликати метод об’єкта, JavaScript спочатку шукає їх у самому об’єкті. Якщо не знаходить – він йде шукати у прототипі цього об’єкта. Якщо і там немає – він йде до прототипу прототипу, і так далі, поки не знайде потрібне або не дійде до самого кінця цього “ланцюжка прототипів”.

Головні переваги:

  1. Економія пам’яті: Уявіть, у вас є 1000 об’єктів “котиків”. Замість того, щоб кожен котик мав свою власну копію методу мяукати(), цей метод може бути визначений один раз у прототипі всіх котиків. Всі 1000 котиків будуть використовувати той самий метод з прототипу. Це значно економить пам’ять!
  2. Успадкування: Прототипи – це механізм, за допомогою якого JavaScript реалізує успадкування. Об’єкти можуть “запозичувати” функціональність у своїх прототипів.

Ключові Поняття для Співбесіди:

1. Прототипний Ланцюжок (Prototype Chain)

  • Що це? Це ланцюжок посилань від об’єкта до його прототипу, від прототипу до його прототипу, і так далі, аж до null (який є кінцевою точкою ланцюжка, зазвичай після Object.prototype).
  • Як працює пошук? Коли ви звертаєтеся до myObject.myProperty, JS шукає так:
    1. В myObject? -> Знайдено!
    2. Ні? Йдемо до прототипу myObject (myObject.__proto__ або Object.getPrototypeOf(myObject)). Є там myProperty? -> Знайдено!
    3. Ні? Йдемо до прототипу прототипу… і так далі.
    4. Якщо дійшли до null і не знайшли -> undefined.

JavaScript

// Приклад ланцюжка
const animal = {
  eats: true,
  walk() {
    console.log("Тварина йде");
  }
};

const rabbit = {
  jumps: true,
  __proto__: animal // Встановлюємо прототип (старий спосіб, не рекомендований для продакшену)
};

// Сучасний спосіб створити об'єкт з певним прототипом:
// const rabbit = Object.create(animal);
// rabbit.jumps = true;

console.log(rabbit.jumps); // true (власна властивість rabbit)
console.log(rabbit.eats);  // true (успадковано від animal)
rabbit.walk();             // "Тварина йде" (успадковано від animal)

console.log(Object.getPrototypeOf(rabbit) === animal); // true
console.log(Object.getPrototypeOf(animal) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null (кінець ланцюжка)

2. __proto__ vs prototype

Це дуже часте питання на співбесідах!

  • __proto__ (або [[Prototype]]): Це властивість самого об’єкта, яка вказує на його прототип. Це те, куди JS дивиться, коли шукає властивості по ланцюжку. Краще використовувати сучасні методи Object.getPrototypeOf() (для читання) та Object.setPrototypeOf() (для запису, але рідко потрібно і може впливати на продуктивність) або Object.create() (для створення об’єкта з заданим прототипом).
  • prototype: Це властивість функції-конструктора. Вона існує тільки у функцій (і класів, які є “синтаксичним цукром” над функціями-конструкторами). Значення цієї властивості (SomeConstructor.prototype) стає прототипом (__proto__) для всіх об’єктів, створених за допомогою цієї функції-конструктора (new SomeConstructor()).

JavaScript

// Функція-конструктор
function Rabbit(name) {
  this.name = name;
}

// Властивість prototype є ТІЛЬКИ у функцій (включаючи конструктори)
console.log(typeof Rabbit.prototype); // "object"

// Додаємо метод до прототипу конструктора
Rabbit.prototype.sayHi = function() {
  console.log(`Привіт, я кролик ${this.name}!`);
};

// Створюємо об'єкт за допомогою конструктора
let bunny = new Rabbit("Банні");

// bunny НЕ має властивості 'prototype'
console.log(bunny.prototype); // undefined

// Але bunny має __proto__, який вказує на Rabbit.prototype
console.log(Object.getPrototypeOf(bunny) === Rabbit.prototype); // true

// Метод sayHi береться з прототипу
bunny.sayHi(); // "Привіт, я кролик Банні!"

3. Функції-Конструктори (Constructor Functions)

  • Це звичайні функції, які викликаються з ключовим словом new.
  • new робить кілька речей:
    1. Створює новий порожній об’єкт {}.
    2. Встановлює __proto__ цього об’єкта на Constructor.prototype.
    3. Викликає функцію-конструктор, встановлюючи this на новий об’єкт.
    4. Повертає новий об’єкт (якщо функція не повернула явно інший об’єкт).

4. Object.create()

  • Це метод, який створює новий об’єкт із зазначеним об’єктом-прототипом. Дуже корисний для явного встановлення прототипного зв’язку.

JavaScript

const catPrototype = {
  meow() {
    console.log("Мяу!");
  }
};

const myCat = Object.create(catPrototype);
myCat.name = "Мурчик";

myCat.meow(); // "Мяу!" (успадковано)
console.log(myCat.name); // "Мурчик" (власна властивість)
console.log(Object.getPrototypeOf(myCat) === catPrototype); // true

5. Класи в ES6 (class)

  • Сучасний синтаксис class — це, по суті, “синтаксичний цукор” над прототипним успадкуванням та функціями-конструкторами. Він робить код більш читабельним і схожим на класи в інших мовах, але під капотом все ще працюють прототипи!
  • Методи, оголошені всередині класу, автоматично додаються до ClassName.prototype.

JavaScript

class Dog {
  constructor(name) {
    this.name = name;
  }

  bark() { // Цей метод опиниться в Dog.prototype
    console.log(`${this.name} гавкає: Гав!`);
  }
}

let buddy = new Dog("Бадді");
buddy.bark(); // "Бадді гавкає: Гав!"

console.log(Object.getPrototypeOf(buddy) === Dog.prototype); // true
console.log(typeof Dog.prototype.bark); // "function"

6. Оператор instanceof

  • Перевіряє, чи присутній Constructor.prototype десь у прототипному ланцюжку об’єкта.

JavaScript

console.log(bunny instanceof Rabbit); // true
console.log(bunny instanceof Object); // true (бо Rabbit.prototype успадковує від Object.prototype)
console.log(myCat instanceof Object);  // true

Підсумок для Співбесіди:

  • Знайте різницю між __proto__ (внутрішнє посилання об’єкта на прототип) і prototype (властивість функції-конструктора, яка стає прототипом для нових об’єктів).
  • Розумійте прототипний ланцюжок: Як JS шукає властивості та методи.
  • Поясніть переваги: Економія пам’яті та механізм успадкування.
  • Вмійте використовувати: Object.create(), функції-конструктори та синтаксис class.
  • Розумійте, що класи — це синтаксичний цукор над прототипами.

Прототипи — це фундаментальна концепція JavaScript. Розуміння того, як вони працюють, не тільки допоможе вам на співбесіді, але й зробить вас кращим розробником, оскільки ви будете глибше розуміти, як влаштована мова.

Сподіваюся, це пояснення було корисним! Успіхів!

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *