Прототипы 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
.
Типы членов объекта
Из нашего предыдущего раздела это означает, что объект может иметь два типа членов:
- Члены экземпляра, также известные как его собственные члены
- Члены прототипа
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