Выявлены все запутанные моменты об объектах JavaScript, так что вы можете перестать их так сильно ненавидеть
Многим новым разработчикам мир JavaScript может показаться довольно утомительным, особенно тем, кто имеет традиционный объектно-ориентированный опыт. Если просто поискать в Google какой-нибудь код JavaScript и объяснения, можно выявить довольно запутанный код и запутанную терминологию. Чтобы упростить задачу, в этой статье будет представлен обзор простого наследования на основе прототипов, чтобы у вас была хорошая основа для программирования с помощью JavaScript.
Просто хочу отметить, что я знаю, что статьи, пытающиеся объяснить объекты JavaScript, были написаны до смерти, но я чувствовал, что никогда не читал статей, которые объясняли бы их полностью или ясно. Они часто сосредотачиваются исключительно на наследовании, упуская из виду другие важные моменты или не приводя достаточного количества примеров. Цель здесь - дать вам краткий обзор объектов JavaScript и наследования прототипов самым простым из возможных способов.
Что такое объекты?
Объекты на самом базовом уровне можно представить себе как список пар ключ / значение, где ключ всегда является строкой, а значение -… ну, чем угодно. Это похоже на то, что вы могли бы назвать «картой» на других языках. Все, что вы вводите в JavaScript, но не является примитивом (см. Последний раздел для получения дополнительной информации о примитивах), является объектом. Объекты упрощают упаковку и перемещение данных, а создание новых более тривиально, чем в других объектно-ориентированных языках, таких как Java / C #.
Говоря об объектах, вы часто можете услышать термин «свойство». Это относится к определенной паре ключ / значение на объекте. Чтобы дать вам представление о том, как выглядят объекты, мы начнем с простого примера объекта с двумя свойствами: age
и weight
.
var Dog = { age: 8, weight: 65 }
Примечание. Наш фрагмент кода только что был примером использования литерала объекта. Объектный литерал строго относится к коду внутри фигурных скобок, включая их. Литерал объекта не является переменной или возвращаемым значением.
Функции - это объекты
Как уже упоминалось, все, что не является примитивом, является объектом, который также включает в себя функции ... Я знаю, странно, правда? Трудно представить себе функции как группу пар ключ / значение. Поскольку функции являются объектами, их часто называют объектами-функциями. Функция-объект - это специальная группа пар ключ / значение с определенными свойствами для выполнения кода и передачи значений. В следующем разделе мы рассмотрим, что это за свойства. Сначала давайте поговорим о том, почему функции важны.
Можно сказать, что у функциональных объектов две основные цели. Если мы хотим создать кусок логики, которая выполняется, мы можем использовать объект-функцию: точно так же, как «методы» в любом другом языке программирования. Следующая цель - это когда JavaScript становится немного напуганным. Если мы хотим создавать объекты и со значениями, и с методами, и, возможно, с некоторой логикой для установки этих значений, мы также будем использовать объекты-функции. Здесь вы можете думать о функциях-объектах как о «классах» в объектно-ориентированных языках (например, Java / C #).
Примечание. Вы услышите термин «метод», который иногда используется в JavaScript. В JavaScript метод относится к объекту-функции, который существует как свойство содержащего объекта.
В стандартном случае функции в JavaScript выглядят как функции на любом другом языке; они выполняют логику фрагментов для выполнения конкретной задачи. В следующем фрагменте bark
- это код, выполняющий объект-функцию.
function bark() { console.log('woof woof') } bark() => 'woof woof'
Если мы хотели упаковать небольшую группу данных, например, наши 2 свойства в объекте Dog
из предыдущего, тогда простой список пар ключ / значение будет работать нормально. Что, если мы хотим создать несколько объектов Dog? Возможно, одни значения должны быть статическими, а другие - динамическими. Вот здесь и пригодятся функциональные объекты. Когда мы вызываем функцию с new
, возвращается объект (также известный как instance-object) со свойствами, установленными с помощью ключевого слова this
внутри функции.
function Dog(age, weight) { this.species = 'Canis Familiaris' this.age = age this.weight = weight this.bark = bark <-- bark() from prev snippet } // Spot and Bingo are 'instance-objects' of Dog var Spot = new Dog(8, 65) var Bingo = new Dog(10, 70) Spot.species => 'Canis Familiaris' // bark is a 'method' of Dog Bingo.bark() => 'woof woof'
Объекты против прототипов
Теперь, когда у вас есть хорошее представление об объектах и, что более важно, об объектах-функциях, давайте поговорим о прототипах. Вы часто слышали, что JavaScript - это язык, основанный на прототипах. Значит ли это, что объекты и прототипы - это одно и то же? Не совсем так. Прототипы - это особый тип объекта, который существует как свойство для объектов-функций. Когда мы пытаемся получить доступ к ключу объекта-функции, JavaScript проверяет его свойство prototype
, чтобы узнать, есть ли оно там. В противном случае он пойдет вверх по цепочке прототипов, чтобы попытаться найти его. Чтобы понять цепочку прототипов, нам нужно узнать о функциях и наследовании.
Функции и наследование
Каждый раз, когда объект-экземпляр возвращается из вызова функции с использованием new
, ему присваивается свойство с ключом __proto__
. Значением этого свойства является свойство prototype
функции, которая его создала.
Bingo.__proto__ === Dog.prototype Spot.__proto__ === Dog.prototype
Если мы попытаемся получить доступ к свойству объекта-экземпляра, а его там нет, JavaScript сначала перейдет к __proto__
и проверит, есть ли оно в прототипе родительской функции. Чтобы увидеть это в действии, давайте установим свойство для ключа prototype
для нашего объекта Dog, и когда мы вызовем Spot['whatever the key name is']
или Bingo['whatever the key name is']
, мы получим то же значение. Это будет работать даже после того, как будут созданы оба экземпляра-объекта dog.
Dog.prototype.bark = function() { console.log('woof woof') } Spot.bark() // => 'woof woof' Bingo.bark() // => 'woof woof'
Установка методов таким образом (в отличие от использования this
внутри функций) особенно полезна, потому что реализация метода будет выполняться только один раз, а не каждый раз, когда вызывается new
. Это сэкономит память и увеличит производительность.
Давайте теперь углубимся в вопросы наследования!
В основе всего наследования JavaScript лежит ключевое слово Object
, которое является объектом-функцией. Все экземпляры-объекты наследуются от него. Когда мы создаем объектный литерал, скрытый JavaScript фактически вызывает new Object()
. __proto__
нового объекта будет указывать на прототип Object
. Таким образом, все объекты, созданные из объектных литералов, на самом деле являются объектами экземпляра Object
. Это предоставляет нам множество полезных методов, таких как hasOwnProperty
, которые могут сказать нам, существует ли свойство для объекта. Если мы попытаемся получить доступ к свойству непосредственно в объекте-функции, JavaScript сначала посмотрит на prototype
, а затем продвинется вверх по цепочке, используя __proto__
на прототипе.
Давайте рассмотрим несколько примеров того, как JavaScript перемещается вверх по цепочке прототипов для доступа к hasOwnProperty
некоторым объектам.
Объект-литерал:
var insect = {legs: 6} // insect.__proto__ === Object.prototype // insect.hasOwnProperty === Object.prototype.hasOwnProperty insect.hasOwnProperty('legs') // => true
Экземпляр-объект:
var Bingo = new Dog() // Bingo.__proto__ === Dog.prototype // Dog.prototype.__proto__ === Object.prototype Bingo.hasOwnProperty('weight') // => true
Непосредственно на Функциональном объекте:
function Foo() { this.something = 'blah' } // Foo.prototype.__proto__ === Object.prototype Foo.hasOwnProperty('name') // => true Foo.hasOwnProperty('something') // => false, set on instance-object not on the function
А как насчет __proto__ объектов-функций?
Как объяснялось, __proto__
помогает связывать объекты с прототипами, от которых они наследуются. А как насчет вызова __proto__
непосредственно на объектах-функциях? На самом деле в JavaScript есть встроенный объект-функция с именем Function
. Свойство __proto__
каждой функции указывает на Function.prototype
, который является функцией, но не имеет свойства prototype
и возвращает undefined
. Function.prototype
определяет поведение по умолчанию, от которого наследуются все функции. Как и все prototype
свойства объектов-функций, он по-прежнему имеет__proto__
, что указывает на Object.prototype
.
Dog.__proto__ === Function.prototype Object.__proto__ === Function.prototype Function.__proto__ === Function.prototype Function.prototype.__proto__ === Object.prototype
Уф, это было много ...
Все это немного сбивало с толку, правда? Может быть, этот рисунок поможет упростить задачу. Обратите внимание, что Object.prototype
- вот откуда все происходит.
Многоуровневое наследование
Когда мы говорим о наследовании, мы обычно думаем об объектах-экземплярах, возвращаемых функциями. С prototype
вы также можете выполнять несколько уровней наследования и наследовать объекты-функции от других объектов-функций. Все, что вам нужно сделать, это установить прототип дочернего объекта-функции на другой экземпляр прототипа родительского объекта-функции. Тогда все свойства родителя будут скопированы. Если родительская функция получает аргументы, такие как возраст и вес в Dog
, используйте .call
, чтобы установить свойство this
для дочернего объекта.
Labrador
наследуется от Dog
:
function Labrador(furColor, age, weight) {
this.furColor = furColor
this.breed = 'labrador'
Dog.call(this, age, weight)
}
Labrador.prototype = Object.create(Dog.prototype)
var Fido = new Labrador('white', 4, 41)
Fido.bark()
Классы
Классы в JavaScript, которые были созданы для ES6, являются просто синтаксическим сахаром поверх объектов-функций. Вместо того, чтобы набирать prototype
снова и снова для определения методов функций, с помощью ключевого слова class
мы можем просто определить группу методов внутри класса. С ключевым словом extends
классы могут наследовать от других классов без необходимости делать Object.create
и Object.call
. Мне больше нравится использовать классы, но имейте в виду, что не все браузеры могут их поддерживать (ES6). Вот почему были созданы такие инструменты, как Babel.
Использование функциональных объектов:
function Dog(age, weight) {
this.age = age
this.weight = weight
}
Dog.prototype.bark = function() {console.log('woof woof')}
function Labrador(furColor, age, weight) {
this.furColor = furColor
this.breed = 'labrador'
Dog.call(this, age, weight)
}
Labrador.prototype = Object.create(Dog.prototype)
Эквивалентно с использованием классов:
class Dog { constructor(age, weight) { this.age = age this.weight = weight } bark() { console.log('woof woof') } } class Labrador extends Dog { constructor(furColor, age, weight) { super(age, weight) this.furColor = furColor this.breed = 'labrador' } }
Объекты против примитивов
Код JavaScript по сути сводится к двум основным типам: примитивы и объекты. В JavaScript есть 5 примитивов: boolean
, number
, string
, null
и undefined
. Примитивы - это просто простые значения без каких-либо свойств. У трех примитивов: boolean
, number
и string
есть аналоги объектов, которые JavaScript будет принудительно использовать во время определенных операций. Например, "some string".length
вызовет new String()
под капотом и обернет объект-экземпляр, возвращаемый строковым примитивом, чтобы можно было получить доступ к свойству length
. Как уже упоминалось, все объекты-экземпляры наследуются от Object
. Таким образом, со строкой вы все равно можете использовать такие методы, как hasOwnProperty
.
// String.prototype.__proto__ === Object.prototype String.hasOwnProperty('length') // => true
Заключение
Да, да, я знаю, что это была головная боль; это казалось бесконечным шквалом prototype
этого и __proto__
того. Лично я считаю, что наследование в JavaScript намного сложнее, чем должно быть, поэтому я использую TypeScript. Тем не менее, если вы работаете над тем, чтобы стать надежным фронтенд-разработчиком, будет полезно знать и заставить вас понять, почему существуют супернаборы JavaScript.
Пожалуйста, нажмите кнопку хлопка, если вы нашли эту статью полезной. Удачного веб-поиска!