Робота з об'єктами

Мова JavaScript базується на простій, заснованій на об'єктах парадигмі. Об'єкт - це колекція властивостей, а властивість - це асоціація між іменем (або ключем) та значенням. Значення властивості може бути функцією, в цьому випадку властивість відома як метод. На додачу до об'єктів, попередньо визначених у веб-переглядачі, ви можете визначати власні об'єкти. Цей розділ описує, як використовувати об'єкти, властивості, функції та методи, і як створювати власні об'єкти.

Огляд об'єктів

Об'єкти у JavaScript, як і у багатьох інших мовах програмування, можна порівняти з об'єктами у реальному житті. Концепію об'єктів у JavaScript можна зрозуміти на прикладі матеріальних об'єктів з реального життя.

У JavaScript об'єкт - це окрема сутність з властивостями та типом. Порівняємо його, для прикладу, з чашкою. Чашка - це об'єкт з властивостями. Чашка має колір, дизайн, вагу, матеріал, з якого вона зроблена, і т.д. Так само, об'єкти JavaScript можуть мати властивості, які визначають їхні характеристики.

Об'єкти та властивості

Об'єкт JavaScript має асоційовані з ним властивості. Властивість об'єкта можна описати як змінну, прикріплену до об'єкта. Властивості об'єкта - це, загалом, те саме, що й звичайні змінні JavaScript, але прикріплені до об'єктів. Властивості об'єкта визначають характеристики об'єкта. Ви звертаєтесь до властивостей об'єкта через просту крапкову нотацію:

objectName.propertyName

Як усі змінні JavaScript, і ім'я об'єкта (яке може бути звичайною змінною), і ім'я властивості чутливі до регістру. Ви можете визначити властивість, присвоївши їй значення. Наприклад, створимо об'єкт на ім'я myCar (моя машина) та дамо йому властивості make (виробник), model (модель) та year (рік) ось так:

var myCar = new Object();
myCar.make = 'Ford';
myCar.model = 'Mustang';
myCar.year = 1969;

Наведений приклад також міг бути написаний за допомогою об'єктного ініціалізатора, який є списком з нуля чи більше розділених комами пар імен властивостей об'єкта та асоційованих з ними значень, записаний у фігурних дужках ({}):

var myCar = {
    make: 'Ford',
    model: 'Mustang',
    year: 1969
};

Неприсвоєні властивості об'єкта мають значення undefined (а не null).

myCar.color; // undefined

До властивостей об'єктів JavaScript також можна звертатись чи присвоювати їх за допомогою дужкової нотації (щоб дізнатись більше, дивіться доступ до властивостей). Об'єкти іноді називають асоціативними масивами, оскільки кожна властивість асоціюється з рядковим значенням, яке можна використати для доступу до неї. Отже, для прикладу, ви можете звертатись до властивостей об'єкта myCar наступним чином:

myCar['make'] = 'Ford';
myCar['model'] = 'Mustang';
myCar['year'] = 1969;

Ім'я властивості об'єкта може бути будь-яким дозволеним рядком JavaScript чи будь-чим, що можна привести до рядка, в тому числі порожній рядок. Однак, будь-яке ім'я властивості, яке не є дозволеним ідентифікатором JavaScript (наприклад, ім'я властивості, що містить пробіл чи дефіс, або починається з цифри) доступне тільки через позначення у квадратних дужках. Ця нотація також дуже корисна, коли імена властивостей мають бути динамічно визначені (коли ім'я властивості не визначене до початку виконання). Приклади наступні:

// створюємо одночасно чотири змінні, розділені комами, 
// та присвоюємо їм значення
var myObj = new Object(),
    str = 'myString',
    rand = Math.random(),
    obj = new Object();

myObj.type              = 'крапковий синтаксис';
myObj['date created']   = 'рядок з пробілом';
myObj[str]              = 'рядкове значення';
myObj[rand]             = 'випадкове число';
myObj[obj]              = 'об\'єкт';
myObj['']               = 'навіть порожній рядок';

console.log(myObj);

Будь ласка, зауважте, що усі ключі, позначені у квадратних дужках, перетворюються на рядки, якщо тільки вони не є символами, оскільки імена властивостей (ключів) об'єктів JavaScript можуть бути тільки рядками або символами (в якийсь момент також будуть додані приватні імена, з розвитком пропозиції щодо полів класу, але вони не використовуватимуться у формі []). Наприклад, у наведеному вище коді, коли ключ obj додається до об'єкта myObj, JavaScript викличе метод obj.toString() та використає отриманий рядок як новий ключ.

Ви також можете звернутись до властивості, використавши рядкове значення, що зберігається у змінній:

var propertyName = 'make';
myCar[propertyName] = 'Ford';

propertyName = 'model';
myCar[propertyName] = 'Mustang';

Ви можете використати дужкову нотацію з циклом for...in для перебору усіх перелічуваних властивостей об'єкта. Для ілюстрації того, як це працює, наступна функція відображає властивості об'єкта, коли ви передаєте об'єкт та ім'я об'єкта в якості аргументів у функцію:

function showProps(obj, objName) {
  var result = ``;
  for (var i in obj) {
    // obj.hasOwnProperty() відфільтровує властивості від ланцюга прототипів об'єкта
    if (obj.hasOwnProperty(i)) {
      result += `${objName}.${i} = ${obj[i]}\n`;
    }
  }
  return result;
}

Отже, виклик функції showProps(myCar, "myCar") поверне наступне:

myCar.make = Ford
myCar.model = Mustang
myCar.year = 1969

Перелік властивостей об'єкта

Починаючи з ECMAScript 5, існують три вбудовані методи перелічити / продивитись властивості об'єкта:

  • цикли for...in
    Цей метод продивляється усі перелічувані властивості об'єкта та його ланцюжок прототипів
  • Object.keys(o)
    Цей метод повертає масив з іменами усіх особистих (не з ланцюга прототипів) перелічуваних властивостей ("ключів") об'єкта o.
  • Object.getOwnPropertyNames(o)
    Цей метод повертає масив, що містить імена усіх особистих властивостей (перелічуваних чи ні) об'єкта o.

До ECMAScript 5 не існувало вбудованого способу перелічити усі властивості об'єкта. Однак, цього можна досягти наступною функцією:

function listAllProperties(o) {
	var objectToInspect;     
	var result = [];
	
	for(objectToInspect = o; objectToInspect !== null; 
           objectToInspect = Object.getPrototypeOf(objectToInspect)) {  
        result = result.concat(
            Object.getOwnPropertyNames(objectToInspect)
        );  
    }
	
	return result; 
}

Це може бути корисним для виявлення "схованих" властивостей (властивостей у ланцюгу прототипів, які недоступні через об'єкт, тому що інша властивість з таким самим іменем зустрічається у ланцюгу раніше). Список лише доступних властивостей можна легко зробити, прибравши дублікати у масиві.

Створення нових об'єктів

JavaScript має чимало попередньо визначених об'єктів. На додачу, ви можете створювати свої власні об'єкти. Ви можете створити об'єкт за допомогою об'єктного ініціалізатора. Або ви можете спочатку створити функцію-конструктор, після чого створювати об'єкти, використовуючи оператор new.

Використання об'єктних ініціалізаторів

На додачу до створення об'єктів за допомогою функції-конструктора, ви можете створювати об'єкти, використовуючи об'єктний ініціалізатор. Використання об'єктних ініціалізаторів іноді називають створенням об'єктів літеральною нотацією. "Об'єктний ініціалізатор" відповідає термінології, що використовується у мові C++.

Синтаксис об'єкта, що використовує об'єктний ініціалізатор, наступний:

var obj = { property_1:   value_1,   // property_# може бути ідентифікатором...
            2:            value_2,   // або числом...
            // ...,
            'property n': value_n }; // або рядком

де obj - ім'я нового об'єкта, кожне ім'я property_i є ідентифікатором (або числом, або рядковим літералом), а кожне значення value_i є виразом, чиє значення присвоюється властивості property_i. Змінна obj та присвоєння є необов'язковими; якщо вам непотрібно звертатись до цього об'єкта будь-де, вам непотрібно присвоювати його змінній. (Зауважте, що вам може знадобитись загорнути об'єктний літерал у круглі дужки, якщо об'єкт з'являється там, де очікується інструкція, щоб літерал не був прийнятий за блочну інструкцію.)

Об'єктні ініціалізатори є виразами, а кожний об'єктний ініціалізатор створює новий об'єкт, коли виконується інструкція, де він знаходиться. Ідентичні об'єктні ініціалізатори створюють окремі об'єкти, які не вважатимуться рівними. Об'єкти створюються так, ніби відбувся виклик new Object(); тобто, об'єкти, створені об'єктними літералами, є екземплярами Object.

Наступна інструкція створює об'єкт та присвоює його змінній x тільки за умови, що вираз cond є правдивим:

if (cond) var x = {greeting: 'привітик'};

Наступний приклад створює об'єкт myHonda з трьома властивостями. Зауважте, що властивість engine також є об'єктом зі своїми властивостями.

var myHonda = {color: 'червоний', wheels: 4, engine: {cylinders: 4, size: 2.2}};

Ви також можете використовувати об'єктні ініціалізатори для створення масивів. Дивіться масивні літерали.

Використання функції-конструктора

Також ви можете створити об'єкт, виконавши ці два кроки:

  1. Визначити тип об'єкта, написавши функцію-конструктор. Загальноприйнято, і на це є причини, писати їх з великої літери.
  2. Створити екземпляр об'єкта за допомогою new.

Щоб визначити тип об'єкта, створіть функцю для типу об'єкта, яка визначає його ім'я, властивості та методи. Наприклад, припустимо, ви хочете створити тип об'єкта для автомобілів. Ви хочете, щоб цей тип об'єкта називався Car, і ви хочете, щоб він мав властивості для виробника, моделі та року. Щоб це зробити, ви б написали наступну функцію:

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

Зверніть увагу на використання this для присвоєння значень властивостям об'єкта на основі значень, переданих у функцію.

Тепер ви можете створити об'єкт на ім'я mycar наступним чином:

var mycar = new Car('Eagle', 'Talon TSi', 1993);

Ця інструкція створює об'єкт mycar та присвоює вказані значення його властивостям. Тоді значенням mycar.make є рядок "Eagle", mycar.year дорівнює цілому число 1993 і т.д.

Ви можете створити довільне число об'єктів Car викликами new. Наприклад,

var kenscar = new Car('Nissan', '300ZX', 1992);
var vpgscar = new Car('Mazda', 'Miata', 1990);

Об'єкт може мати властивість, яка сама є іншим об'єктом. Наприклад, припустимо, ви визначаєте об'єкт person (людина) наступним чином:

function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}

і далі створюєте два екземпляра об'єкта person наступним чином:

var rand = new Person('Rand McKinnon', 33, 'M');
var ken = new Person('Ken Jones', 39, 'M');

Тоді ви можете переписати визначення Car, щоб включити властивість owner (власник), яка приймає об'єкт person ось так:

function Car(make, model, year, owner) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.owner = owner;
}

Тепер, щоб створити нові об'єкти, ви зробите наступне:

var car1 = new Car('Eagle', 'Talon TSi', 1993, rand);
var car2 = new Car('Nissan', '300ZX', 1992, ken);

Зверніть увагу, що, замість передачі літералу рядка чи цілого числа під час створення нових об'єктів, наведені вище інструкції передають об'єкти rand та ken в якості значень власників. Тепер, якщо вам потрібно дізнатись ім'я власника car2, ви можете звернутись до наступної властивості:

car2.owner.name

Зауважте, що ви завжди можете додати властивість до попередньо визначеного об'єкта. Наприклад, інструкція

car1.color = 'чорний';

додає властивість color (колір) до car1, та присвоює їй значення "чорний." Однак, це не впливає на всі інші об'єкти. Щоб додати нову властивість до усіх об'єктів того самого типу, ви маєте додати властивість до визначення типу об'єкта Car.

Використання методу Object.create

Об'єкти також можна створювати методом Object.create(). Цей метод може бути дуже корисним, тому що дозволяє обирати прототип для об'єкта, який ви хочете створити, без необхідності визначати функцію-конструктор.

// Властивості об'єкта Animal (тварина) та інкапсуляція методу
var Animal = {
  type: 'Безхребетні', // Значення властивості за замовчуванням
  displayType: function() {  // Метод, що виводитиме тип тварини
    console.log(this.type);
  }
};

// Створити нову тварину з назвою animal1 
var animal1 = Object.create(Animal);
animal1.displayType(); // виведе: Безхребетні

// Створити нову тварину з назвою fish (риба)
var fish = Object.create(Animal);
fish.type = 'Риби';
fish.displayType(); // виведе: Риби

Наслідування

Усі об'єкти у JavaScript успадковуються від принаймні одного іншого об'єкта. Об'єкт, від якого наслідується інший об'єкт, відомий як прототип, а успадковані властивості можна знайти у об'єкті конструктора prototype. Щоб дізнатись більше, дивіться Наслідування та ланцюжок прототипів.

Індексування властивостей об'єкта

Ви можете звернутись до властивості об'єкта або за іменем властивості, або за його оригінальним індексом. Якщо ви з самого початку визначили властивість за іменем, ви завжди маєте звертатись до неї за іменем, а якщо ви визначили властивість за індексом, ви завжди маєте звертатись до неї за індексом.

Це обмеження працює, коли ви створюєте об'єкт та його властивості функцією-конструктором (як ми робили раніше з типом об'єкта Car) та коли ви явно визначаєте окремі властивості (наприклад, myCar.color = "червоний"). Якщо ви початково визначили властивість об'єкта з індексом, як от myCar[5] = "25 mpg", ви далі звертаєтесь до властивості лише у вигляді myCar[5].

Винятком з цього правила є подібний до масиву об'єкт з HTML, такий як подібний до масиву об'єкт forms. Ви завжди можете посилатись на об'єкти у цих подібних до масивів об'єктах або за їхнім порядковим номером (в залежності від їхнього розташування у документі), або за іменем (якщо воно визначене). Наприклад, якщо другий тег <FORM> у документі має атрибут NAME, що дорівнює "myForm", ви можете звернутись до форми document.forms[1], або document.forms["myForm"], або document.forms.myForm.

Визначення властивостей для типу об'єкта

Ви можете додати властивість до попередньо визначеного типу об'єкта за допомогою властивості prototype. Це визначає властивість, спільну для усіх об'єктів вказаного типу, а не лише для одного екземпляру об'єкта. Наступний код додає властивість color (колір) до усіх об'єктів типу Car (автомобіль), після чого присвоює значення властивості color об'єкта car1.

Car.prototype.color = null;
car1.color = 'чорний';

Щоб дізнатись більше, дивіться властивість prototype об'єкта Function у довіднику JavaScript.

Визначення методів

Метод - це функція, асоційована з об'єктом, або, простіше кажучи, метод - це властивість об'єкта, яка є функцією. Методи визначаються так само, як звичайні функції, за винятком того, що вони мають бути асоційовані з властивістю об'єкта. Дивіться більше подробиць у визначенні методів. Приклад наступний:

objectName.methodname = functionName;

var myObj = {
  myMethod: function(params) {
    // ...зробити щось
  }

  // ТАКЕ ТЕЖ ПРАЦЮЄ

  myOtherMethod(params) {
    // ...зробити щось інше
  }
};

де objectName - це існуючий об'єкт, methodname - це ім'я, яке ви присвоюєте методу, а functionName - це ім'я функції.

Далі ви можете викликати метод у контексті об'єкта наступним чином:

object.methodname(params);

Ви можете визначати методи для типу об'єкта, додавши визначення методів у конструктор об'єкта. Ви можете визначити функцію, що буде форматувати та відображати властивості попередньо визначених об'єктів Car; наприклад,

function displayCar() {
  var result = `Чудовий автомобіль ${this.year} ${this.make} ${this.model}`;
  pretty_print(result);
}

де pretty_print - функція, що відображатиме горизонтальне правило та рядок. Зверніть увагу на використання this для посилання на об'єкт, до якого належить метод.

Ви можете зробити цю функцію методом Car, додавши інструкцію

this.displayCar = displayCar;

до визначення об'єкта. Отже, повне визначення Car виглядатиме так

function Car(make, model, year, owner) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.owner = owner;
  this.displayCar = displayCar;
}

Тоді ви можете викликати метод displayCar для кожного об'єкта наступним чином:

car1.displayCar();
car2.displayCar();

Використання this у посиланнях на об'єкт

JavaScript має спеціальне ключове слово this, яке ви можете використати всередині метода, щоб вказати на поточний об'єкт. Наприклад, припустимо, ви маєте 2 об'єкта, Manager та Intern. Кожен об'єкт має свої власні name (ім'я), age (вік) та job (роботу). У функції sayHi(), зверніть увагу, є this.name. Додані до 2-х об'єктів, вони можуть бути викликані та повернуть 'Привіт, мене звуть', а далі додають значення name з цього конкретного об'єкта. Як показано нижче. 

const Manager = {
  name: "Джон",
  age: 27,
  job: "Програміст"
}
const Intern= {
  name: "Бен",
  age: 21,
  job: "Програміст-інтерн"
}

function sayHi() {
    console.log('Привіт, мене звуть', this.name)
}

// додаємо функцію sayHi до обох об'єктів
Manager.sayHi = sayHi;
Intern.sayHi = sayHi; 

Manager.sayHi() // Привіт, мене звуть Джон
Intern.sayHi() // Привіт, мене звуть Бен

this посилається на об'єкт, в якому знаходиться. Ви можете створити нову функцію під назвою howOldAmI(), яка виводить повідомлення про те, скільки цій людині років. 

function howOldAmI (){
  console.log('Мені ' + this.age + ' років.')
}
Manager.howOldAmI = howOldAmI;
Manager.howOldAmI() // Мені 27 років.

Визначення гетерів та сетерів

Гетер - це метод, який отримує значення конкретної властивості. Сетер - це метод, який присвоює значення конкретній властивості. Ви можете визначати гетери та сетери на будь-якому існуючому базовому об'єкті чи об'єкті, створеному користувачем, якщо він підтримує додавання нових властивостей. 

Гетери та сетери можуть бути або

  • визначені за допомогою об'єктних ініціалізаторів, або
  • додані пізніше до будь-якого об'єкта у будь-який час за допомогою методу додавання гетерів чи сетерів.

При визначенні гетерів та сетерів за допомогою об'єктних ініціалізаторів вам потрібно лише додати перед методом-гетером get, а перед методом-сетером set. Звісно, гетер не повинен очікувати на параметри, а сетер очікує рівно на один параметер (нове значення, яке треба присвоїти). Наприклад:

var o = {
  a: 7,
  get b() { 
    return this.a + 1;
  },
  set c(x) {
    this.a = x / 2;
  }
};

console.log(o.a); // 7
console.log(o.b); // 8 <-- В цьому місці запускається метод get b().
o.c = 50;         //   <-- В цьому місці запускається метод set c(x).
console.log(o.a); // 25

Властивості об'єкта o наступні:

  • o.a — число
  • o.b — гетер, який вертає o.a плюс 1
  • o.c — сетер, який присвоює o.a половину значення, присвоєного o.c

Будь ласка, зауважте, що імена функцій гетерів та сетерів, визначені у об'єктному літералі за допомогою "[gs]et властивість()" (на відміну від __define[GS]etter__ ), не є іменами самих гетерів, хоча синтаксис [gs]et propertyName(){ } і міг ввести вас в оману.

Гетери та сетери також можуть бути додані до об'єкта в будь-який момент після створення за допомогою методу Object.defineProperties. Першим параметром цього методу є об'єкт, на якому ви хочете визначити гетер чи сетер. Другим параметром є об'єкт, чиї імена властивостей є іменами гетерів чи сетерів і чиї значення властивостей є об'єктами для визначення функцій гетерів чи сетерів. Ось приклад, який визначає такі самі гетер та сетер, які використовувались у попередньому прикладі:

var o = { a: 0 };

Object.defineProperties(o, {
    'b': { get: function() { return this.a + 1; } },
    'c': { set: function(x) { this.a = x / 2; } }
});

o.c = 10; // Запускає сетер, який присвоює 10 / 2 (5) властивості 'a'
console.log(o.b); // Запускає гетер, який видає a + 1, тобто 6

Яку з двох форм обирати, залежить від вашого стилю програмування та наявної задачі. Якщо ви вже користуєтесь об'єктним ініціалізатором при визначенні прототипу, ви, скоріше за все, обиратимете першу форму. Ця форма є більш компактною та природньою. Однак, якщо ви додаєте гетери та сетери пізніше — оскільки не писали прототип чи окремий об'єкт — тоді друга форма є єдино можливою. Друга форма, можливо, найкраще відображає динамічну природу JavaScript — але вона може зробити код важким для читання та розуміння.

Видалення властивостей

Ви можете видалити неуспадковані властивості оператором delete. Наступний код демонструє, як прибрати властивість.

// Створює новий об'єкт, myobj, з двома властивостями, a та b.
var myobj = new Object;
myobj.a = 5;
myobj.b = 12;

// Прибирає властивість a, залишивши у myobj лише властивість b.
delete myobj.a;
console.log ('a' in myobj); // виведе: "false"

Ви також можете використати delete, щоб видалити глобальну змінну, якщо ключове слово var не використовувалось для оголошення змінної:

g = 17;
delete g;

Порівняння об'єктів

У JavaScript об'єкти належать до типу посилань. Два окремі об'єкти ніколи не дорівнюють один одному, навіть якщо мають однакові властивості. Лише порівняння об'єкта з самим собою поверне true.

// Дві змінні, два окремих об'єкти з однаковими властивостями
var fruit = {name: 'яблуко'};
var fruitbear = {name: 'яблуко'};

fruit == fruitbear; // вертає false
fruit === fruitbear; // вертає false
// Дві змінні, один об'єкт
var fruit = {name: 'яблуко'};
var fruitbear = fruit;  // Присвоїти fruitbear посилання на об'єкт fruit

// Тут fruit та fruitbear вказують на один об'єкт
fruit == fruitbear; // вертає true
fruit === fruitbear; // вертає true

fruit.name = 'виноград';
console.log(fruitbear); // output: { name: "виноград" }, замість { name: "яблуко" }

Щоб дізнатись більше щодо операторів порівняння, дивіться Оператори порівняння.

Див. також