Прототип похож на отношения. Объект может указывать на другой объект как на свой прототип.

Рассмотрим следующий пример:

let human = {
  hasRedHat: true,
};

let mario = {
  name: "Mario",
};

console.log(mario.hasRedHat); // undefined

Переменная mario указывает на объект, у которого нет свойства hasRedHat. Регистрация этого свойства вызовет поведение JavaScript по умолчанию и вернет undefined.

Однако вы можете указать JavaScript продолжать искать отсутствующее свойство в другом объекте. Это делается путем указания специального свойства __proto__, также известного как прототип объекта.

Фактически любой объект JavaScript может выбрать в качестве прототипа другой объект:

let human = {
  hasRedHat: true,
};

let mario = {
  name: "Mario",
  __proto__: human, // Look for other properties in the "human" object
};

console.log(mario.hasRedHat); // true

В приведенном выше примере вы начинаете с просмотра провода mario, ведущего к объекту. Сначала вы посмотрите, есть ли у этого объекта свойство hasRedHat. Ответ отрицательный, но у него есть прототип. Затем вы следуете по проводу __proto__, который ведет к другому объекту (human). Вы смотрите на этот объект, чтобы увидеть, имеет ли он свойство hasRedHat. Ответ положительный, поэтому вы следуете по проводу hasRedHat, ведущему к true. Следовательно, результатом mario.hasRedHat является true.

Обратите внимание, что это не означает, что mario имеет свойство hasRedHat. Его объект-прототип, тот самый, на который указывает human. С помощью __proto__ вы указываете JavaScript запрашивать другой объект.

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

Думайте о прототипе как об отношении между объектами, в котором один объект указывает на другой объект как на свой прототип. Эта последовательность объектов называется цепочкой прототипов объекта и не может быть замкнутой.

let mammal = {
  hasBrain: true,
};

let human = {
  __proto__: mammal,
  hasRedHat: true,
};

let mario = {
  __proto__: human,
  name: "Mario",
};

console.log(mario.hasBrain); // true

Вы можете видеть, что JavaScript будет искать свойство сначала в объекте mario, затем в его прототипе, затем в прототипе этого объекта и так далее. Вы получите undefined только в том случае, если у вас закончились прототипы и вы еще не нашли то свойство, которое искали.

Затенение

Рассмотрим следующий пример, в котором оба объекта имеют свойство с именем hasRedHat:

Рассмотрим следующий пример, в котором оба объекта имеют свойство с именем hasRedHat:

let human = {
  hasRedHat: false,
};

let mario = {
  __proto__: human,
  hasRedHat: true, // This object has its own "hasRedHat" property
};

console.log(human.hasRedHat); // false
console.log(mario.hasRedHat); // true

Если бы у mario не было собственного свойства hasRedHat, вам пришлось бы смотреть на прототип. Но поскольку объект, на который указывает mario, имеет собственное свойство hasRedHat, вам не нужно искать ответ. Как только вы найдете недвижимость, вы остановите поиск.

Если вы хотите проверить, есть ли у объекта собственный провод свойств с определенным именем, вы можете вызвать встроенную функцию hasOwnProperty. Он возвращает true для собственных свойств и не смотрит на прототипы:

console.log(human.hasOwnProperty("hasRedHat")); // true
console.log(mario.hasOwnProperty("hasRedHat")); // true

Назначение

Когда вы читаете свойство, не существующее в объекте, вы продолжаете искать его в цепочке прототипов. Если не найдете, получите undefined.

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

Рассмотрим следующий пример:

let human = {
  hasRedHat: false,
};

let mario = {
  __proto__: human,
};

console.log(human.hasRedHat); // false
console.log(mario.hasRedHat); // false

mario.hasRedHat = true;

console.log(human.hasRedHat); // false
console.log(mario.hasRedHat); // true

Перед присваиванием оба выражения приводят к false. Затем вы выполняете это назначение:

mario.hasRedHat = true;

Присваивания происходят на самом объекте. Таким образом, mario.hasRedHat = true создает собственное новое свойство с именем hasRedHat для объекта, на который указывает mario. На прототип это не влияет. В результате human.hasRedHat по-прежнему false, а mario.hasRedHat теперь true.

Прототип объекта

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

let obj = {};
console.log(obj);

Хотя вы создали то, что кажется пустым объектом, obj.__proto__ не является null или undefined. Он имеет скрытый провод __proto__, который по умолчанию указывает на Prototype объекта. Это позволяет вам получить доступ к встроенным методам объекта:

let mario = {
  name: "Mario",
};

console.log(mario.hasOwnProperty); // (function)
console.log(mario.toString); // (function)

Эти встроенные свойства являются обычными свойствами прототипа объекта, к которым вы можете получить доступ, поскольку прототипом объекта mario является прототип объекта.

Прототип объекта — это объект без прототипа. Вам не нужно будет создавать подобные объекты, но полезно знать, что он существует и создается путем настройки свойства __proto__.

let nintendo = {
  __proto__: null,
};

console.log(nintendo.hasOwnProperty); // undefined
console.log(nintendo.toString); // undefined

В приведенном выше примере будет создан объект, не имеющий прототипа и, как следствие, даже не имеющий встроенных методов объекта.

Загрязнение прототипа

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

Мутация общего прототипа называется загрязнением прототипа:

let obj = {};
obj.__proto__.vehicle = "Standard Kart";

let mario = {
  name: "Mario",
};

let luigi = {
  name: "Luigi",
};

console.log(mario.vehicle); // "Standard Kart"
console.log(luigi.vehicle); // "Standard Kart"

Вы изменили прототип объекта, добавив к нему свойство vehicle. В результате любой объект будет использовать один и тот же vehicle.

В прошлом. Загрязнение прототипов было популярным методом добавления новых функций в JavaScript. Однако веб-сообщество больше не рекомендует это.

И еще: __proto__ против prototype

Просматривая документацию MDN, вы столкнетесь со свойством prototype. До того, как в JavaScript была добавлена ​​поддержка классов, их обычно писали как функции, возвращающие объекты. Всякий раз, когда вы хотели поделиться прототипом с некоторыми общими методами, вам приходилось вручную добавлять __proto__ к каждому объекту.

function SuperMarioMushroom() {
  return { type: "power-up" };
}

let mushroomProto = {
  consume() {
    console.log("I got the power!");
  },
};

let mushroom1 = SuperMarioMushroom();
mushroom1.__proto__ = mushroomProto;
mushroom1.consume();

let mushroom2 = SuperMarioMushroom();
mushroom2.__proto__ = mushroomProto;
mushroom2.consume();

Чтобы решить эту проблему, в JavaScript было добавлено ключевое слово new. Если вы используете ключевое слово new перед вызовом функции, объект создается автоматически, без необходимости возвращать его из функции, и становится доступным как this.

Кроме того, __proto__ объекта будет установлено на то, что вы добавите в свойство prototype функции, без необходимости устанавливать его вручную.

function SuperMarioMushroom() {
  this.type = "power-up";
}

// prototype approach 1
SuperMarioMushroom.prototype = {
  consume() {
    console.log("I got the power!");
  },
};

// prototype approach 2
SuperMarioMushroom.prototype.consume = function () {
  console.log("I got the power!");
};

let mushroom1 = new SuperMarioMushroom(); // __proto__: SuperMarioMushroom.prototype
mushroom1.consume();

let mushroom2 = new SuperMarioMushroom(); // __proto__: SuperMarioMushroom.prototype
mushroom2.consume();

В приведенном выше примере показано, как свойство prototype функции позволяет настроить __proto__ объектов, которые вы получаете с помощью вызовов new.

Хотя свойство prototype по-прежнему доступно во встроенных функциях, в современном JavaScript вместо этого вы обычно используете синтаксис class с конструктором:

class SuperMarioMushroom {
  constructor() {
    this.type = "power-up";
  }
  consume() {
    console.log("I got the power!");
  }
}

let mushroom1 = new SuperMarioMushroom(); // __proto__: SuperMarioMushroom.prototype
mushroom1.consume();

let mushroom2 = new SuperMarioMushroom(); // __proto__: SuperMarioMushroom.prototype
mushroom2.consume();

Несмотря на то, что использование синтаксиса __proto__ не рекомендуется, помните, что под капотом mushroom1.consume() по-прежнему находит свойство consume, ища его через __proto__.

Использование прототипов для создания модели наследования классов стало настолько распространенным, что JavaScript добавил синтаксис class в качестве соглашения, которое скрывает как __proto__, так и prototype.