Прототипы JavaScript на простом английском языке

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

Вы можете спросить: «Разве в современном JavaScript нет ключевого слова class

Да, это так, но это просто «сахар» по сравнению с прототипами, и понимание того, как работают прототипы, может избавить вас от большого стресса при отладке кода JavaScript.

JavaScript странный, но понимание его делает его менее странным, на мой взгляд (если вы верите, что мое мнение имеет значение :)).

Начните с Почему

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

С помощью этого «чертежа» мы можем создать столько объектов, сколько нам нужно. Как мы вскоре увидим, хотя прототипы работают по-разному, они также позволяют нам повторно использовать код.

Объекты и их члены

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

var song = {
  title: "Bed of Stones",
  artiste: "Asa",
  getDescription : () => {
    return "Best song on earth"
  }
}

Членами этого объекта являются title, artiste и getDescription. Члены, которые ссылаются на функцию, называются методами ( getDescription в этом примере), а остальные считаются свойствами объекта. Однако обычно все члены называются свойствами.

Прототипы

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

Что это хотя бы значит?

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

Объект привязан к своему прототипу через внутреннее свойство, которое большинство браузеров выставляет как __proto__.

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

var song = {
  title: "Bed of Stones",
  artiste: "Asa"
}
console.log(typeof song.__proto__) // prints "object"

Этот объект-прототип также содержит члены, некоторые из них представлены на диаграмме ниже:

Когда мы пишем,

song.toString()

Движок JavaScript сначала проверит члены объекта song на наличие toString, а если не найдет, то проверит прототип объекта песни. В этом случае он находит там свойство toString.

Типы членов объекта

Из нашего предыдущего раздела это означает, что объект может иметь два типа членов:

  1. Члены экземпляра, также известные как его собственные члены
  2. Члены прототипа

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

console.log(song.hasOwnProperty("title")) //prints true console.log(song.hasOwnProperty("artiste")) //prints true console.log(song.hasOwnProperty("toString")) //prints false

hasOwnProperty(fieldName) запрашивает движок JavaScript, является ли fieldName экземпляром рассматриваемого объекта.

С другой стороны, оператор in в JavaScript проверяет как элементы экземпляра, так и прототипа.

console.log("title" in song) //true console.log("artiste" in song) //true console.log("toString" in song) //true

Тест: Как мы можем получить доступ к hasOwnProperty в объекте song, даже если он не определен в нем?

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

Как упоминалось ранее, по умолчанию все объекты в JavaScript имеют свой прототип из глобального Object. Мы говорим, что они являются экземплярами Object.

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

Объект.создать()

Рассмотрим следующий фрагмент кода:

var song = {
  title: "Bed of Stones",
  artiste: "Asa"
}
// creates a new object which prototype is 'song'
var media = Object.create(song)
console.log(Object.getPrototypeOf(media)) // [object Object] { //artiste: "Asa", title: "Bed of Stones" }

Object.create(song) создаст новый объект и установит его прототип на объект song. Вместо чтения прототипа объекта через свойство __proto__ мы также можем использовать Object.getPrototypeOf, как показано в примере.

Вы можете передать другой объект в Object.create, чтобы добавить определенные свойства экземпляра к создаваемому объекту. Например:

// creates a new object that has this structure { type: "Mp3"} and // whose prototype is the song object
var media = Object.create(song, { type: {value: "Mp3" } })
console.log(media.type) // prints "Mp3"
console.log(media.title) // prints "Bed of Stones"

Функции конструктора

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

Чтобы создать экземпляр функции-конструктора, вы используете ключевое слово new с вызовом функции, например:

function Bar() {}
var bar = new Bar() // creates a new object

Когда используется ключевое слово new, JavaScript неявно присваивает ключевое слово this новому создаваемому объекту. Таким образом, мы можем использовать this для ссылки на создаваемый объект:

function Bar() {
  this.name = "bar"
}
var bar = new Bar()
console.log(bar.name) // "bar"

Функция-конструктор prototype Свойство

Каждая функция в JavaScript имеет специальное свойство prototype. Вы можете спросить, поскольку функции тоже являются объектами, является ли свойство prototype тем же самым, что и свойство __proto__?

Да. Объект, на который указывает свойство prototype функции-конструктора, не является прототипом функции. Так что же это?

Помните, я упоминал, что функцию-конструктор можно использовать для создания нового объекта… и установки прототипа нового объекта? Прототипом нового объекта является объект, на который указывает свойство prototype функции-конструктора.

Позвольте мне перефразировать это по-другому.

  • Каждая функция имеет свойство prototype
  • Вы можете создать объект (экземпляр) функции, используя ключевое слово new
  • Прототип объекта устанавливается в свойство prototype функции-конструктора.

Позвольте мне завершить это следующим фрагментом кода:

function Song(title, artiste) {
  this.title = title
  this.artiste = artiste
}
Song.prototype.getDescription = function() {
  return this.title + " by " + this.artiste
}
var song1 = new Song("Bed of Stones", "Asa")
var song2 = new Song("We all Lose Sometimes", "Brymo")
console.log(song1.title) // "Bed of Stones"
console.log(song2.artiste) // "Brymo"
console.log(song2.getDescription()) // "We all Lose Sometimes by Brymo"

song1 и song2 используют одну и ту же цепочку прототипов. Обратите внимание, что Song.prototype имеет свойство constructor, созданное JavaScript для указания на функцию-конструктор, в данном случае Song.

Диаграмма выше была упрощена, Song сама имеет свойство __proto__, это показано на диаграмме ниже:

Заключение

Вот это да. Вы зашли так далеко. Приятно, приятно :) Мы обсудили все, что, на мой взгляд, необходимо для понимания и лучшей отладки программ на JavaScript в отношении прототипов.

Если вы найдете какую-либо ошибку, пожалуйста, укажите на нее.

Счастливого кодирования

Бонус: instanceof против typeof

Предположим, у нас есть

function Bar() {
  this.name = "bar"
}
var bar = new Bar()

Когда мы пишем

console.log(bar instanceof Bar) // prints true

bar instanceof Bar означает, что мы задаем вопрос движку JavaScript,

Существует ли Bar.prototype в цепочке прототипов bar?

Ответ, в данном случае, да (правда).

Оператор instanceof проверяет, появляется ли свойство прототипа функции-конструктора в цепочке прототипов объекта.

Угадайте, что это напечатает?

Снова ответ true, потому что Object.prototype существует в цепочке прототипов bar.

оператор typeof

Оператор typeof возвращает строку, указывающую тип операнда. Возвращаемая строка может быть любой из "symbol", "bigint", "number", "string", "boolean", "object", "function" или «неопределенной».

Пример:

typeof 12 === 'number'; // returns true

Первоначально опубликовано на https://solathecoder.hashnode.dev.

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