Прототипы

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

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

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

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

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('construtor') // 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: Прототипы
Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →