ООП в JavaScript

Матеріал з Вікі ЦДУ
Перейти до: навігація, пошук

Теоретичний опис принципів ООП в JavaScript може заняти дуже багато часу, і викликати багато суперечок навколо питання чи взагалі існують класи в JavaScript. Я не буду на цьому зупинятися, а відразу дам відповідь: Класів в JavaScript НЕМАЄ

Загальний Опис

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

  • вбудовані об'єкти виконуючої системи
  • об'єкти середовища, в якому виконується сценарій
  • користувацькі об'єкти

об'єкт JavaScript -- це неупорядкований набір властивостей.

Метод -- це властивість, що є функцією. Приклад синтаксису доступу до властивості об'єкту:


імя_об'єкта.імя_властивості

або


імя_об'єкта["імя_властивості"]

Останній синтаксис використовується оператором for ... in. Кожне властивість складається з назви, значення і набору наступних атрибутів:

Атрибут

Опис Атрибуту

DontEnum

Чи повинна властивість попадати в перелічення при обході оператором for..in

DontDelete

Заборона видалення даної властивості. Спроба програмно видалити дану властивість буде проигноровано.

ReadOnly

Заборона зміни цієї властивості. Спроба програмно змінити дану властивість буде проигноровано.

Нова властивість об'єкту створюється просто присвоюванням йому значення. Нехай, наприклад, ми вже створили об'єкт MyObj. Задамо йому ім'я, та, наприклад, якесь значення:


MyObj.name = "Мій Об'єкт"; 
MyObj.value = 256;

Тепер наш об'єкт має 2 властивості: name та value. У таких властивостей, створених користувачем, всі перераховані вище атрибути скинуті в false. Надалі ми можемо змінювати значення цих властивостей або переглядати.

Створення об'єкта

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

Створення об'єктів у JavaScript відбувається приблизно так само, але тут оголошення конструктора збігається з оголошенням класу. Іншими словами, ми визначаємо конструктор як функцію, яка створює об'єкти з заданим початковим набором властивостей і їх значень. Потім ми так само створюємо об'єкти викликом операції new імя_конструктора (...). Або оголошуемо змінну якій без явного визначення функції присвоюємо ряд властивостей, реально створюється анонімна функція, яка створює об'єкт.

Існує два способи створення нових об'єктів у JavaScript, а саме:

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

Створення об'єктів за допомогою ініціалізатора

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

(Властивість: значення [, властивість: значення])

Властивість - ідентифікатор, що задає ім'я властивості; значення - вираз, що задає значення цієї властивості.

Наприклад, об'єкт MyObj з попереднього прикладу може бути створений так:

var MyObj = {name: "Dead", value: "256"};

Можливо конструювати об'єкт в об'єкті. Додамо ще одну властивість об'єкта MyObj, яка називається options і сама є об'єктом:

var MyObj = {name: "Dead", value: "256",
 options: {enabled: true, vud: false}};

Створення об'єктів за допомогою конструктора

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

  1. Ім'я функції задає ім'я створюваного "класу" об'єктів;
  2. Тіло функції повинно містити присвоювання початкових значень властивостям і методам створюваного об'єкту.

Наприклад, конструктор для об'єкта MyObj з попереднього прикладу може мати наступний вигляд:

function MyObj(name, value) 
{
 this.name = name;
 this.value = value;
}

Зверніть увагу на використання операції this для доступу до властивостей об'єкта. Його особливості будуть розглянуті далі.

Тепер для створення нових об'єктів MyObj достатньо викликати цей конструктор в операції new, наприклад:

var myObjL = new MyObj("Indigo", 132);

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

function myOpt(enbl, vud) 
{
 this.enabled = enbl; 
 this.vud = vud;
}
function MyObj(name, value, options) 
{
 this.name = name;
 this.value = value;
 this.options = options;
}
var myOpt = new myOpt(true, false);
var myObjL = new MyObj("Indigo", 132, myOpt);

Для доступу до властивостей властивості options використовується нотація myObjL.options.enabled.

Ключове слово this

Ключове слово this в JavaScript працює своєрідно, не так, як в інших мовах, він не прив'язується статично ні до якого об'єкту, а залежить від контексту виклику.
Існує 4 випадки значення операції:

  • Режим конструктора, якщо функція визивається через new як конструктор об'єкту, то this ставиться на стрворюваний об'єкт;
  • Метод об'єкту, якщо функція запущена як властивість об'єкту, то в this буде посилання на цей об'єкт;
  • Apply/Call, якщо функція визивається через методи Apply та Call, при такому визові функції this буде встановлено в об'єкт, що визивається;
  • Простий виклик, при цьому виклику this стає ріним глобальному об'єкту (в браузері це window).

Створення методів

Оскільки методи є різновидом властивостей, вони створюються так само, як описано вище. Наприклад, ми можемо додати до конструктора об'єктів MyObj метод aboutObj, який буде виводити на екран браузера інформацію про властивості цього об'єкта:

function showObj() 
{
 document.write("Об'єкт: " + this.name + " " + this.version);
}
function MyObj(name, version) 
{
 this.name = name;
 this.version = version;
 this.aboutObj = showObj;
}

В подальшому ми можемо викликати цей метод:

myObjL.showObj();

При бажанні можна записати короче:

function MyObj(name, version) {
 this.name = name;
 this.version = version;
 this.aboutObj = function() 
 {
   document.write("Об'єкт: " + this.name + " " + this.version);
 }
}

Прототипи об'єктів

Більшість об'єктно-орієнтованих мов (наприклад, Java і C + +) засновані на двох базових поняттях: класи об'єктів і екземпляри (instances) об'єктів.

  1. Клас об'єктів - це абстрактне поняття, що описує всі властивості даного класу (в Java ці властивості називаються полями й методами, а в C + + членами класу, але суть від цього не змінюється).
  2. Екземпляр об'єкту - це реалізація класу, тобто конкретний об'єкт, наділений всіма властивостями даного класу.

JavaScript, на відміну від цих мов, заснований на прототипах і не проводить відмінності між двома наведеними поняттями: у ньому є тільки об'єкти. Деяким аналогом класу тут виступає прототип об'єкта, який визначає початковий набір властивостей нового об'єкта. У процесі виконання програми об'єкт може отримувати нові властивості; більше того, він може сам виступати як прототип при створенні нових об'єктів.

Припустимо, що ми хочемо в процесі виконання сценарію додати нову властивість security (безпека) класу об'єктів Options (підкреслимо ще раз - класу об'єктів, а не окремому його представнику myOptions). Для цього використовується властивість prototype об'єкта Function:

Options.prototype.security = null;

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

myObjL.options.security = "Велика";

Для видалення властивостей об'єктів використовується операція delete, наприклад:

delete Options.prototype.security;

Видалення об'єктів

Можна видалити раніше створений об'єк:

delete myObjL;

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

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

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

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


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

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

8bba.png

var person = Object.create(null)

// Here we are reusing the previous getter/setter functions

Object.defineProperty(person, 'name', { get: get_full_name
, set: set_full_name
, configurable: true
, enumerable: true })

// And adding the `greet' function person.greet = function (person) {

return this.name + ': Why, hello there, ' + person + '.'

}

// Then we can share those behaviours with Mikhail // By creating a new object that has it's Prototype property // pointing to `person'. var mikhail = Object.create(person)

mikhail.first_name = 'Mikhail'
mikhail.last_name = 'Weiß'
mikhail.age = 19
mikhail.gender = 'Male'

// And we can test whether things are actually working. // First, `name' should be looked on `person'

mikhail.name

// => 'Mikhail Weiß'

// Setting `name' should trigger the setter

mikhail.name = 'Michael White'

// Such that `first_name' and `last_name' now reflect the // previously name setting.

mikhail.first_name

// => 'Michael'

mikhail.last_name

// => 'White'

// `greet' is also inherited from `person'.

mikhail.greet('you')

// => 'Michael White: Why, hello there, you.'

// And just to be sure, we can check which properties actually // belong to `mikhail' Object.keys(mikhail) // => [ 'first_name', 'last_name', 'age', 'gender' ]

Примітивні вбудовані об'єкти

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

Об'єкт

Опис об'єкту

Об'єкт

Опис об'єкту

Array

Масиви

Math

Математичні функції та константи

Boolean

Логічні об'єкти

Number

Числові об'єкти

Date

Дата та час

Object

Прототип інших об'єктів

Error

Виключення

RegExp

Регулярні вирази

Function

Функції

String

Рядкові об'єкти

Getter-и і setter-и

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

Object.defineProperty(mikhail, 'first_name', {

value: 'Mikhail'
, writable: true })

Object.defineProperty(mikhail, 'last_name', {

value: 'Weiß'
, writable: true })

Потім ми опишемо загальний спосіб отримання і установки відразу двох властивостей за один раз - назвемо їх об'єднання name:

// () → String

// Returns the full name of object.

function get_full_name() {

return this.first_name + ' ' + this.last_name

}

// (new_name:String) → undefined

// Sets the name components of the object, from a full name.

function set_full_name(new_name) {

var names
names = new_name.trim().split(/\s+/)
this.first_name = names[⁣'0'] ||
this.last_name = names['1'] ||

}

Object.defineProperty(mikhail, 'name',

{ get: get_full_name
, set: set_full_name
, configurable: true
, enumerable: true })

Тепер, кожен раз коли ми спробуємо дізнатися значення властивості name нашого друга насправді викликається функція get_full_name:

mikhail.name

// => 'Mikhail Weiß'

mikhail.first_name

// => 'Mikhail'

mikhail.last_name

// => 'Weiß'

mikhail.last_name = 'White'

mikhail.name

// => 'Mikhail White'

Також можемо встановити name об'єкта, звернувшись до відповідного властивості, але насправді виклик set_full_name виконає всю брудну роботу:

mikhail.name = 'Michael White'

mikhail.name

// => 'Michael White'

mikhail.first_name

// => 'Michael'

mikhail.last_name

// => 'White'

Крім того, слід враховувати що getter-и і setter-и зазвичай використовуються в інших мовах для інкапсуляції.

Літерали

Простий спосіб створити об'єкт полягає у використанні літерального синтаксису JavaScript. Літеральний об'єкт визначає новий об'єкт.

У кожному разі, синтаксис літеральних об'єктів дозволяє визначати прості об'єкти і ініціалізувати їх властивості. Перепишемо приклад створення об'єкта Mikhail:

var mikhail = { first_name: 'Mikhail'

, last_name: 'Weiß'
, age: 19
, gender: 'Male'

// () → String

// Returns the full name of object.

, get name() {

return this.first_name + ' ' + this.last_name }

// (new_name:String) → undefined

// Sets the name components of the object,

// from a full name.

, set name(new_name) { var names

names = new_name.trim().split(/\s+/)

this.first_name = names['0'] ||
this.last_name = names['1'] || }

}

Невалідний імена властивостей можуть бути укладені в лапки. Враховуйте, що запис для getter / setter в літеральном вигляді визначається анонімними функціями. Якщо ви хочете пов'язати раніше оголошену функцію з getter / setter, то ви повинні використовувати метод Object.defineProperty.

Подивимося на загальне правила літерального синтаксису:

<object-literal>  ::= "{" <property-list> "}"

<property-list>  ::= <property> ["," <property>]*

<property>  ::= <data-property>

| <getter-property>
| <setter-property>

<data-property>  ::= <property-name> ":" <expression>

<getter-property> ::= "get" <identifier>

<function-parameters>
<function-block>

<setter-property> ::= "set" <identifier>

<function-parameters>
<function-block>

<property-name>  ::= <identifier>

| <quoted-identifier>

Літеральні об'єкти можуть з'являтися всередині виразів в JavaScript. Через деякій неоднозначності новачки іноді плутаються:


// This is a block statement, with a label:

{ foo: 'bar' }

// => 'bar'

// This is a syntax error (labels can't be quoted):

{ "foo": 'bar' }

// => SyntaxError: Invalid label

// This is an object literal (note the parenthesis to force // parsing the contents as an expression):

({ "foo": 'bar' })

// => { foo: 'bar' }

// Where the parser is already expecting expressions, // object literals don't need to be forced. E.g.: var x = { foo: 'bar' }

fn({foo: 'bar'})
return { foo: 'bar' }

1, { foo: