Vue Front-end Инженер

Vue Front-end Инженер

Роадмап навыков для прокачки

Цепочка прототипов

Язык JavaScript (ES5)Прототипы и наследованиеОсновное

Основная идея

Цепочка прототипов — это механизм наследования в JavaScript, где каждый объект имеет ссылку на свой прототип ([[Prototype]]). При обращении к свойству объекта поиск идёт сначала в самом объекте, затем в его прототипе, и так далее до null.

Ключевые аспекты

  • [[Prototype]] — внутреннее свойство, ссылка на прототип объекта
  • proto — геттер/сеттер для доступа к [[Prototype]] (нестандартный, но поддерживается)
  • Object.prototype — конец цепочки для большинства объектов
  • null — абсолютный конец цепочки прототипов

Плюсы и минусы

ПлюсыМинусы
Экономия памяти — методы в прототипеСложность понимания
Динамическое наследованиеДлинные цепочки = замедление
Гибкость измененийИзменение прототипа влияет на все объекты

Частые ошибки на собеседованиях

  • Путают __proto__ (ссылка на прототип объекта) и prototype (свойство функции-конструктора)
  • Не знают, что цепочка заканчивается null, а не Object.prototype
  • Забывают, что запись свойства НЕ поднимается по цепочке — создаётся в самом объекте
  • Не понимают, что Object.prototype.__proto__ === null

Введение и проблематика

JavaScript — прототипно-ориентированный язык. В отличие от классических ООП-языков (Java, C++), наследование в JS реализовано через цепочку прототипов (prototype chain).

Каждый объект в JavaScript имеет скрытую ссылку [[Prototype]] на другой объект — свой прототип. Эта цепочка заканчивается на null.

Почему это важно?

  • Прототипы — основа наследования в JavaScript
  • Позволяют экономить память (общие методы в прототипе)
  • Понимание необходимо для работы с ООП в JS

Базовая теория

Что такое прототип?

Прототип — это объект, от которого текущий объект наследует свойства и методы.

Code Example 1: Что выведут rabbit.jumps и rabbit.eats? Откуда берётся значение eats?

js
var animal = {
  eats: true
};
 
var rabbit = {
  jumps: true
};
 
// Устанавливаем прототип
rabbit.__proto__ = animal;
 
// Теперь rabbit наследует от animal
console.log(rabbit.jumps); // true (собственное)
console.log(rabbit.eats);  // true (унаследовано)

Как работает поиск свойства

graph TD A[rabbit.eats] --> B{Есть в rabbit?} B -->|Нет| C{Есть в rabbit.__proto__?} C -->|Да| D[Возвращаем animal.eats] C -->|Нет| E{Есть в Object.prototype?} E -->|Нет| F[Возвращаем undefined]

Code Example 2: Как JavaScript ищет свойство rabbit.eats? Какие шаги выполняет движок?

js
var animal = { eats: true };
var rabbit = { jumps: true };
rabbit.__proto__ = animal;
 
// Поиск rabbit.eats:
// 1. Ищем в rabbit — нет
// 2. Ищем в rabbit.__proto__ (animal) — есть!
// 3. Возвращаем true

Практические примеры

Стандартная цепочка прототипов

Code Example 3: Какова цепочка прототипов для обычного объекта obj? Что выведут все console.log?

js
var obj = { x: 1 };
 
// Цепочка прототипов:
// obj -> Object.prototype -> null
 
console.log(obj.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
 
// obj наследует методы от Object.prototype
console.log(obj.toString()); // "[object Object]"
console.log(obj.hasOwnProperty('x')); // true

Многоуровневая цепочка

Code Example 4: Какова цепочка прототипов для longEar? Что выведет каждый console.log?

js
var animal = {
  eats: true,
  walk: function() {
    console.log('Идёт');
  }
};
 
var rabbit = {
  jumps: true
};
rabbit.__proto__ = animal;
 
var longEar = {
  earLength: 10
};
longEar.__proto__ = rabbit;
 
// Цепочка: longEar -> rabbit -> animal -> Object.prototype -> null
 
console.log(longEar.earLength); // 10 (собственное)
console.log(longEar.jumps);     // true (от rabbit)
console.log(longEar.eats);      // true (от animal)
console.log(longEar.toString);  // function (от Object.prototype)

Визуализация цепочки

graph LR A[longEar] -->|__proto__| B[rabbit] B -->|__proto__| C[animal] C -->|__proto__| D[Object.prototype] D -->|__proto__| E[null]

Чтение vs Запись свойств

⚠️

При ЧТЕНИИ свойства поиск идёт по цепочке прототипов. При ЗАПИСИ свойство создаётся в самом объекте.

Code Example 5: Что выведут rabbit.eats и animal.eats после записи? Почему запись не затрагивает прототип?

js
var animal = {
  eats: true,
  walk: function() {
    console.log('Идёт');
  }
};
 
var rabbit = {};
rabbit.__proto__ = animal;
 
// Чтение — идёт по цепочке
console.log(rabbit.eats); // true (из animal)
 
// Запись — создаёт свойство в rabbit
rabbit.eats = false;
 
console.log(rabbit.eats);  // false (собственное)
console.log(animal.eats);  // true (не изменилось!)
 
// Теперь у rabbit есть собственное свойство
console.log(rabbit.hasOwnProperty('eats')); // true

Цепочка для встроенных типов

Массивы

Code Example 6: Какова цепочка прототипов для массива? Откуда массив наследует методы push и toString?

js
var arr = [1, 2, 3];
 
// Цепочка: arr -> Array.prototype -> Object.prototype -> null
 
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
 
// arr наследует методы массива
arr.push(4);        // от Array.prototype
arr.toString();     // от Object.prototype

Функции

Code Example 7: Какова цепочка прототипов для функции? Откуда функция наследует метод call?

js
function fn() {}
 
// Цепочка: fn -> Function.prototype -> Object.prototype -> null
 
console.log(fn.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
 
// fn наследует методы функции
fn.call();    // от Function.prototype
fn.toString(); // от Function.prototype (переопределён)

Пограничные кейсы

Object.create(null) — объект без прототипа

Code Example 8: Чем объект без прототипа отличается от обычного? Что выведет noProto.toString?

js
var noProto = Object.create(null);
 
console.log(noProto.__proto__); // undefined
console.log(noProto.toString);  // undefined
 
// Нет унаследованных методов — чистый словарь
noProto['hasOwnProperty'] = 'значение'; // Безопасно!

Изменение прототипа влияет на все объекты

Code Example 9: Что выведет rabbit.eats после изменения animal.eats? Почему?

js
var animal = { eats: true };
var rabbit = {};
rabbit.__proto__ = animal;
 
console.log(rabbit.eats); // true
 
// Изменяем прототип
animal.eats = false;
 
console.log(rabbit.eats); // false — изменилось!
 
// Добавляем новый метод
animal.sleep = function() {
  console.log('Спит');
};
 
rabbit.sleep(); // "Спит" — метод доступен!

Проверка прототипов

isPrototypeOf()

js
var animal = { eats: true };
var rabbit = {};
rabbit.__proto__ = animal;
 
console.log(animal.isPrototypeOf(rabbit)); // true
console.log(Object.prototype.isPrototypeOf(rabbit)); // true
console.log(Object.prototype.isPrototypeOf(animal)); // true

Object.getPrototypeOf()

js
var animal = { eats: true };
var rabbit = {};
rabbit.__proto__ = animal;
 
console.log(Object.getPrototypeOf(rabbit) === animal); // true
console.log(Object.getPrototypeOf(animal) === Object.prototype); // true

Производительность

⚠️

Длинные цепочки прототипов замедляют поиск свойств. Держите цепочки короткими.

js
// ❌ Слишком длинная цепочка
var a = {};
var b = Object.create(a);
var c = Object.create(b);
var d = Object.create(c);
var e = Object.create(d);
// e -> d -> c -> b -> a -> Object.prototype -> null
 
// Поиск свойства проходит через все 6 уровней

Вопросы интервьюера

Q: Что такое цепочка прототипов?

Это механизм наследования, где каждый объект имеет ссылку на прототип. При поиске свойства JS идёт по цепочке от объекта к его прототипу и далее до null.

Q: Чем заканчивается цепочка прототипов?

Значением null. Обычно: объект -> ... -> Object.prototype -> null.

Q: Что происходит при записи свойства?

Свойство создаётся в самом объекте, не затрагивая прототип. Поиск по цепочке происходит только при чтении.

Q: Как создать объект без прототипа?

Object.create(null) — создаёт объект с [[Prototype]] = null.


Источники

Code Example 1: Setting a prototype

❓ Что выведут rabbit.jumps и rabbit.eats? Откуда берётся значение eats?

js
var animal = {
  eats: true
};
 
var rabbit = {
  jumps: true
};
 
rabbit.__proto__ = animal;
 
console.log(rabbit.jumps);
console.log(rabbit.eats);

Code Example 2: Property lookup

❓ Как JavaScript ищет свойство rabbit.eats? Какие шаги выполняет движок?

js
var animal = { eats: true };
var rabbit = { jumps: true };
rabbit.__proto__ = animal;
 
console.log(rabbit.eats);

Code Example 3: Standard prototype chain

❓ Какова цепочка прототипов для обычного объекта obj? Что выведут все console.log?

js
var obj = { x: 1 };
 
console.log(obj.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__ === null);
 
console.log(obj.toString());
console.log(obj.hasOwnProperty('x'));

Code Example 4: Multi-level chain

❓ Какова цепочка прототипов для longEar? Что выведет каждый console.log?

js
var animal = {
  eats: true,
  walk: function() {
    console.log('Идёт');
  }
};
 
var rabbit = {
  jumps: true
};
rabbit.__proto__ = animal;
 
var longEar = {
  earLength: 10
};
longEar.__proto__ = rabbit;
 
console.log(longEar.earLength);
console.log(longEar.jumps);
console.log(longEar.eats);
console.log(longEar.toString);

Code Example 5: Reading vs Writing properties

❓ Что выведут rabbit.eats и animal.eats после записи? Почему запись не затрагивает прототип?

js
var animal = {
  eats: true,
  walk: function() {
    console.log('Идёт');
  }
};
 
var rabbit = {};
rabbit.__proto__ = animal;
 
console.log(rabbit.eats);
 
rabbit.eats = false;
 
console.log(rabbit.eats);
console.log(animal.eats);
 
console.log(rabbit.hasOwnProperty('eats'));

Code Example 6: Array prototype chain

❓ Какова цепочка прототипов для массива? Откуда массив наследует методы push и toString?

js
var arr = [1, 2, 3];
 
console.log(arr.__proto__ === Array.prototype);
console.log(Array.prototype.__proto__ === Object.prototype);
 
arr.push(4);
arr.toString();

Code Example 7: Function prototype chain

❓ Какова цепочка прототипов для функции? Откуда функция наследует метод call?

js
function fn() {}
 
console.log(fn.__proto__ === Function.prototype);
console.log(Function.prototype.__proto__ === Object.prototype);
 
fn.call();
fn.toString();

Code Example 8: Object.create(null)

❓ Чем объект без прототипа отличается от обычного? Что выведет noProto.toString?

js
var noProto = Object.create(null);
 
console.log(noProto.__proto__);
console.log(noProto.toString);
 
noProto['hasOwnProperty'] = 'значение';

Code Example 9: Changing prototype affects all objects

❓ Что выведет rabbit.eats после изменения animal.eats? Почему?

js
var animal = { eats: true };
var rabbit = {};
rabbit.__proto__ = animal;
 
console.log(rabbit.eats);
 
animal.eats = false;
 
console.log(rabbit.eats);
 
animal.sleep = function() {
  console.log('Спит');
};
 
rabbit.sleep();