Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос нашим менторам. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Прототипы

В этом уроке дается небольшое введение в тему прототипов. Полностью раскрыть её одним уроком невозможно. Не хватит даже целого курса. Прототипы познаются только через практику, перемешанную с теоретической подготовкой. Поэтому не переживайте, если не всё поймёте с первого раза, это нормально.

Прототипы — это механизм, который оказывает основное влияние на то, как работают объекты в JavaScript. Сами они напрямую в коде используются редко (и обычно только в библиотеках), но их знание важно для понимания поведения кода и отладки. Особенно при работе с классами, которые мы изучим дальше по курсу. В этом уроке мы затронем только самые основы. Глубоко разобраться с прототипами поможет наш курс, указанный в дополнительных материалах.

В JavaScript с каждым объектом связан прототип. Прототип – это обычный объект, хранящийся в специальном служебном поле [[prototype]] (к этому полю невозможно обратиться напрямую). Его можно извлечь так:

const date = new Date();
// Эта функция извлекает прототип объекта из самого объекта
Object.getPrototypeOf(date); // Date {}

// В прототипе хранится не конструктор
// Что там хранится – узнаем дальше
Object.getPrototypeOf(date) === Date // false

const numbers = [1, 2];
Object.getPrototypeOf(numbers); // [] – отображение отличается, но это массив

// Прототипы есть и у конструкторов, которые мы определяем сами
function Company(name) {
  this.name = name;
}

const company = new Company();
Object.getPrototypeOf(company); // Company {}

Зачем он нужен? Чтобы ответить на этот вопрос, нужно разобраться с тем, как в JavaScript работает вызов свойств. До этого момента все было просто: если в объекте есть свойство, то его можно вызывать и получить значение, если свойства нет, то при обращении к нему мы получим undefined. Это правда, но не вся. JavaScript устроен хитрее и ищет свойство не только в самом объекте. Если свойство для объекта не определено, то JavaScript смотрит в прототип и пытается выяснить есть ли это свойство у него. Если есть, то возвращается его значение.

В реальности процесс ещё сложнее. Если свойство не найдено в прототипе, то JavaScript смотрит прототип прототипа и так далее до конца цепочки, а у неё есть конец: последний прототип в цепочке прототипов всегда null. На базе этого механизма реализуется наследование. Эта тема выходит за рамки текущего урока.

Прототип есть даже у обычных JavaScript-объектов:

Object.getPrototypeOf({}); // {} – это и есть Object

Именно по этой причине даже пустые объекты "содержат" (помним, что их содержит прототип) свойства и методы:

const obj = {}; // То же самое можно сделать так: const obj = new Object();
// Это функция-конструктор из которой был получен текущий объект, в данном случае Object
obj.constructor; // [Function: Object]
// У obj нет своего собственного свойства constructor, оно пришло из прототипа
obj.hasOwnProperty('constructor') // false
obj.hasOwnProperty('name'); // false
obj.name = 'hexlet';
// Имя есть в самом объекте, потому что мы его только что добавили
obj.hasOwnProperty('name'); // true

Проверить работу прототипов достаточно легко, изменив их. Доступ к прототипу можно получить не только из объектов, но и из свойства prototype конструктора, который эти объекты создаёт:

function Company(name) {
  this.name = name;
}

// Одно и то же, полученное разными способами
// Company.prototype === Object.getPrototypeOf(new Company())

// Добавляем свойство getName (делаем его методом)
Company.prototype.getName = function getName() {
  // this по-прежнему зависит от контекста, в котором вызывается
  return this.name;
}

const company = new Company('Hexlet');
// Свойство доступно!
console.log(company.getName()); // => Hexlet

При этом никто не мешает заменить значение свойства getName в конкретном объекте. Это никаким образом не отразится на других объектах, так как они извлекают getName из прототипа:

const company1 = new Company('Hexlet');
const company2 = new Company('Google');
company2.getName = function getName() {
  return 'Alphabet';
}

// Этот вызов возьмет свойство из самого объекта
company2.getName(); // Alphabet
// Этот вызов возьмет значение свойства из прототипа
company1.getName(); // Hexlet

Создание свойств через прототип – и есть правильный способ создания своих абстракций в JavaScript. Любая новая абстракция, которая нам нужна в коде, должна выглядеть как конструктор и прототип, наполненный нужными свойствами.

Что даёт этот механизм?

Самое простое – расширение ядра языка и библиотек без прямого доступа к исходному коду. Прототипы – невероятно гибкий механизм, который позволяет менять всё что угодно из любого места программы (в рантайме, то есть во время работы). Например, мы можем добавить или заменить любые методы в любых объектах самого языка.

const numbers1 = [1, 3];

// Как только выполнится этот код, все массивы,
// включая уже созданные, обзаведутся методом last
Array.prototype.last = function last() {
  // Такое обращение законно, ведь this это ссылка на сам объект,
  // который в нашем случае массив
  return this[this.length - 1];
}

numbers1.last(); // 3

const numbers2 = [10, 0, -2];
numbers2.last(); // -2

// Пример замены
// запрещенный прием, никогда так не делайте в реальном коде
Array.prototype.map = function map() {
  return 'Ehu!';
}

numbers1.map(); // "Ehu!"

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

// Очень злой код, который ломает работу метода push
Array.prototype.push = function push(value) {
  return this.unshift(value);
}

const numbers = [1, 2];
numbers.push(3);
console.log(numbers); // => [3, 1, 2] !!!

Но если использовать этот механизм с умом, то можно получить очень много хорошего. Например прототипы активно используются в тестировании, они помогают подменить вызовы нежелательных методов, которые выполняют HTTP-запросы. Другой популярный вариант – полифилы. Это код, который добавляет в старые версии JavaScript возможности из новых версий, но не все, конечно — только те, что появляются в виде свойств и методов.

P.S. Теперь вы знаете, почему в документации все имена функций описаны так: Number.prototype.toLocaleString().


Дополнительные материалы

  1. JS: Прототипы

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят менторы из команды Хекслета или другие студенты.

Для полного доступа к курсу нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, даст возможность обращаться за помощью к менторам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».