Основы объектов в JavaScript

В этой статье мы рассмотрим объекты в JavaScript. Мы будем разбирать основы синтаксиса объектов JavaScript и заново изучим некоторые возможности JavaScript, которые мы уже исследовали ранее на курсе, подтвердив тот факт, что большая часть функциональности, с которой мы уже столкнулись, в действительности является объектами.

Необходимые знания: Элементарная компьютерная грамотность, базовое понимание HTML и CSS, знакомство с основами JavaScript (см. Первые шаги и Структурные элементы).
Цель: Понимать основу теории перед началом объектно-ориентированного программирования, как это связано с JavaScript ("большинство сущностей являются объектами"), и как начать работу с объектами JavaScript.

Основы объектов

Объект — это совокупность связанных данных и/или функциональных возможностей. Обычно состоят из нескольких переменных и функций, которые называются свойства и методы, если они находятся внутри объектов. Разберём пример, чтобы показать, как они выглядят.

Чтобы начать, скопируйте себе oojs.html файл. В нём содержится очень мало: <script> элемент для написания в нём исходного кода. Мы будем использовать это как основу для изучения основ синтаксиса объектов. Во время работы с этим примером у вас должна быть открытая консоль JavaScript инструментов разработчика, готовая к вводу некоторых команд.

Как и во многих случаях в JavaScript, создание объекта часто начинается с определения и инициализации переменной. Попробуйте ввести следующий код JavaScript в ваш файл, а затем сохраните файл и обновите страницу браузера:

js
const person = {};

Если вы введёте person в текстовое JS консоль и нажмёте клавишу Enter, должен получиться следующий результат:

js
Object { }

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

js
const person = {
  name: ["Bob", "Smith"],
  age: 32,
  gender: "male",
  interests: ["music", "skiing"],
  bio: function () {
    alert(
      this.name[0] +
        " " +
        this.name[1] +
        " is " +
        this.age +
        " years old. He likes " +
        this.interests[0] +
        " and " +
        this.interests[1] +
        ".",
    );
  },
  greeting: function () {
    alert("Hi! I'm " + this.name[0] + ".");
  },
};

После сохранения и обновления, попробуйте ввести что-нибудь следующее в консоль JavaScript браузера:

js
person.name;
person.name[0];
person.age;
person.interests[1];
person.bio();
person.greeting();

Как видите, наш объект содержит некоторые данные, а также несколько методов. У нас же с помощью простого синтаксиса есть доступ к ним.

Примечание: Если у вас возникли проблемы с применением файла в работе, попробуйте сравнить ваш код с нашей версией — см. oojs-finished.html (также see it running live). Одна из распространённых ошибок, когда вы начинаете с объектами ставить запятую в конце последнего члена — это приводит к ошибке.

Итак что здесь происходит? Объект состоит из нескольких элементов, каждый из которых имеет своё название (пример name и age выше), и значение (пример ['Bob', 'Smith'] и 32). Каждая пара название/значение должны быть разделены запятой, а название и значение в каждом случае разделяются двоеточием. Синтаксис всегда следует этому образцу:

js
const objectName = {
  member1Name: member1Value,
  member2Name: member2Value,
  member3Name: member3Value,
};

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

Такие объекты называются литералами объекта (object literal) — мы буквально вписали все содержимое объекта для его создания. Этот способ сильно отличается от объектов реализованных классами, которые мы рассмотрим позже.

Очень часто для создания объекта используется литерал объекта когда вам нужно каким-то образом перенести ряд структурированных, связанных элементов данных, например, отправляя запрос на сервер, для размещения их в базе данных. Отправка одного объекта намного эффективнее, чем отправка нескольких элементов по отдельности, и с ним легче работать чем с массивом, если требуется идентифицировать отдельные элементы по имени.

Точечная запись (Dot notation)

Выше вы получили доступ к свойствам и методам используя точечную запись (dot notation). Имя объекта (person) действует как пространство имён (namespace) — оно должно быть введено первым, для того чтобы получить доступ ко всему что заключено (encapsulated) внутри объекта. Далее вы пишете точку, затем элемент, к которому хотите получить доступ — это может быть имя простого свойства, элемент массива, или вызов одного из методов объекта, например:

js
person.age;
person.interests[1];
person.bio();

Внутренние пространства имён (Sub-namespaces)

Можно даже сделать значением элемента объекта другой объект. Например, попробуйте изменить значение свойства name с такого

js
name: ['Bob', 'Smith'],

на такое

js
name : {
  first: 'Bob',
  last: 'Smith'
},

Здесь мы фактически создаём внутреннее пространство имён (sub-namespace). Это звучит сложно, но на самом деле это не так — для доступа к этим элементам вам нужно сделать один дополнительный шаг с ещё одной точкой. Попробуйте в консоли браузера следующее:

js
person.name.first;
person.name.last;

Важно: На этом этапе вам также нужно будет пересмотреть код метода и изменить все экземпляры с

js
name[0];
name[1];

на

js
name.first;
name.last;

Иначе ваши методы больше не будут работать.

Скобочная запись (Bracket notation)

Существует другой способ получить свойства объекта — использовать скобочную запись (bracket notation). Вместо написания этого кода:

js
person.age;
person.name.first;

Вы можете использовать следующий

js
person["age"];
person["name"]["first"];

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

Запись элементов в объект

До сих пор мы рассматривали только возврат (или получение) элементов объекта — вы так же можете установить (обновить) значение элемента объекта просто объявив элемент, который вы хотите установить (используя точечную или скобочную запись), например:

js
person.age = 45;
person["name"]["last"] = "Cratchit";

Попробуйте ввести эти строки, а затем снова верните элементы, чтобы увидеть, как они изменились

js
person.age;
person["name"]["last"];

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

js
person["eyes"] = "hazel";
person.farewell = function () {
  alert("Bye everybody!");
};

Теперь вы можете проверить ваши новые элементы:

js
person["eyes"];
person.farewell();

Одним из полезных аспектов скобочной записи является то, что с её помощью можно динамически задавать не только значения элементов, но и их имена. Предположим, что мы хотим, чтобы пользователи могли хранить пользовательские типы данных, введя имя и значение элемента в два следующих поля? Мы могли бы получить эти значения следующим образом:

js
let myDataName = nameInput.value;
let myDataValue = nameValue.value;

Затем мы можем добавить имя и значение этого нового элемента в объект person таким образом:

js
person[myDataName] = myDataValue;

Чтобы проверить это, попробуйте добавить следующие строки в свой код, после закрывающей скобки объекта person :

js
let myDataName = "height";
let myDataValue = "1.75m";
person[myDataName] = myDataValue;

Теперь попробуйте сохранить и обновить, затем введите следующее в консоль браузера:

js
person.height;

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

Что такое "this"?

Возможно, вы заметили что-то странное в наших методах. Посмотрите на этот пример:

js
greeting: function() {
  alert('Hi! I\'m ' + this.name.first + '.');
}

Вы, вероятно, задаётесь вопросом, что такое "this"? Ключевое слово this, ссылается на текущий объект, внутри которого пишется код — поэтому в нашем случае this равен объекту person. Но почему просто не написать person? Как вы увидите в статье Object-oriented JavaScript for beginners (en-US) (Объектно-ориентированный JavaScript для начинающих), когда мы начинаем создавать конструкторы и т.д., this очень полезен — он всегда будет гарантировать, что используется верное значение, когда контекст элемента изменяется (например, два разных экземпляра объекта person могут иметь разные имена, но захотят использовать своё собственное имя при приветствии.

Давайте проиллюстрируем, что мы имеем в виду, с упрощённой парой объектов person :

js
const person1 = {
  name: "Chris",
  greeting: function () {
    alert("Hi! I'm " + this.name + ".");
  },
};

const person2 = {
  name: "Brian",
  greeting: function () {
    alert("Hi! I'm " + this.name + ".");
  },
};

В этом случае, person1.greeting() выведет "Hi! I'm Chris.". person2.greeting(), с другой стороны, выведет "Hi! I'm Brian.", хотя код метода одинаковый в обоих случаях. Как мы сказали ранее, this равен объекту, внутри которого находится код — это не очень полезно, когда вы пишите литералы объектов вручную, но оно действительно помогает, когда вы генерируете объекты динамически (например используя конструкторы). Это станет понятнее чуть позже.

Все это время вы использовали объекты

Пока вы проходили эти примеры, вы вероятно заметили, что точечная запись, которую вы использовали, выглядит очень знакомо. Это потому, что вы использовали её на протяжении всего курса! Каждый раз, когда мы работаем над примером, использующим встроенный API браузера или объект JavaScript, мы использовали объекты, потому что такие функции построены с использованием тех же структур объектов, которые мы здесь рассматривали, хотя и более сложные, чем наши собственные пользовательские примеры.

Поэтому, когда вы использовали строковые методы, такие как:

js
myString.split(",");

Вы использовали метод доступный в экземпляре класса String. Каждый раз создавая строку в вашем коде, эта строка автоматически создаётся как экземпляр String, и поэтому имеет несколько общих методов/свойств, доступных на нем.

Когда вы обращались к объектной модели документа (DOM), используя следующие строки:

js
const myDiv = document.createElement("div");
const myVideo = document.querySelector("video");

Вы использовали методы доступные в экземпляре класса Document. Для каждой загруженной веб-страницы создаётся экземпляр Document, называемый document, который представляет всю структуру страницы, её содержимое и другие функции, такие как URL-адрес. Опять же, это означает, что он имеет несколько общих методов/свойств, доступных на нем.

То же самое относится и к любому другому встроенному объекту/API, который вы использовали — Array, Math, и т. д.

Обратите внимание, что встроенные объекты/API не всегда создают экземпляры объектов автоматически. Как пример, Notifications API — который позволяет новым браузерам запускать системные уведомления, требует, чтобы вы создавали новый экземпляр объекта с помощью конструктора для каждого уведомления, которое вы хотите запустить. Попробуйте ввести следующее в консоль JavaScript:

js
const myNotification = new Notification("Hello!");

Опять же, мы рассмотрим конструкторы в следующей статье.

Примечание: Полезно подумать о том, как объекты взаимодействуют посредством передачи сообщений - когда объекту требуется другой объект для выполнения какого-либо действия, он часто отправляет сообщение другому объекту через один из его методов и ждёт ответа, который мы знаем как возвращаемое (return) значение.

Резюме

Поздравляем, вы достигли конца нашей первой статьи о объектах JS, теперь у вас должно быть хорошее представление о том, как работать с объектами в JavaScript - в том числе создавать свои собственные простые объекты. Вы также должны понимать, что объекты очень полезны в качестве структур для хранения связанных данных и функциональности - если бы мы пытались отслеживать все свойства и методы в нашем объекте person как отдельные переменные и функции, это было неэффективно, и мы бы рисковали столкнуться с другими переменными и функциями с такими же именами. Объекты позволяют нам безопасно хранить информацию в своём собственном блоке, вне опасности.

В следующей статье мы начнём рассматривать теорию объектно-ориентированного программирования (ООП) и как эти техники могут быть использованы в JavaScript