Ми хочемо зробити цей проєкт з відкритим кодом доступним для людей у всьому світі.

Допоможіть перекласти цей підручник вашою мовою!

Об’єкти зазвичай створюються для представлення сутностей реального світу, таких як користувачі, замовлення тощо:

let user = { name: "Іван", age: 30 };

І в реальному світі користувач може діяти: вибрати щось із кошика для покупок, авторизуватися, виходити із системи тощо.

Дії представлені в JavaScript функціями у властивостях об’єкта.

Приклади методів

Для початку навчімо user вітатися:

let user = { name: "Іван", age: 30 }; user.sayHi = function() { alert("Привіт!"); }; user.sayHi(); // Привіт!

Тут ми щойно використали Function Expression (функціональний вираз) для створення функції та присвоїли її властивості user.sayHi об’єкта.

Потім ми викликали її завдяки user.sayHi(). Користувач тепер може говорити!

Функція, яка є властивістю об’єкта, називається його методом.

Отже, ми отримали метод sayHi об’єкта user.

Звичайно, ми могли б використовувати попередньо оголошену функцію як метод, наприклад:

let user = { // ... }; // спочатку створимо функцію function sayHi() { alert("Привіт!"); } // потім додамо її як метод user.sayHi = sayHi; user.sayHi(); // Привіт!
Object-oriented programming

Коли ми пишемо наш код, використовуючи об’єкти для представлення сутностей, це називається об’єктно-орієнтоване програмування, скорочено: “ООП”.

ООП є великою предметною областю і цікавою наукою саме по собі. Як правильно обрати сутності? Як організувати взаємодію між ними? Це архітектура, і на цю тему є чудові книги, такі як “Шаблони проєктування: елементи багаторазового об’єктно-орієнтованого програмного забезпечення” Е. Гамми, Р. Хелма, Р. Джонсона, Дж. Віссідеса або “Об’єктно-орієнтований аналіз та дизайн з застосунками” Г. Буча та ін.

Скорочений запис методу

Існує коротший синтаксис для методів в літералі об’єкта:

// цей об’єкт робить те ж саме user = { sayHi: function() { alert("Привіт!"); } }; // скорочений метод виглядає краще, чи не так? user = { sayHi() { // те ж саме що й "sayHi: function(){...}" alert("Привіт!"); } };

Як було показано, ми можемо опустити "function" і написати просто sayHi().

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

“this” в методах

Як правило, метод об’єкта повинен отримувати доступ до інформації, що зберігається в об’єкті, для виконання своєї роботи.

Наприклад, коду всередині user.sayHi() може знадобитися ім’я, що зберігається в об’єкті user.

Для доступу до інформації всередині об’єкта метод може використовувати ключове слово this.

Значенням this є об’єкт “перед крапкою”, який використовується для виклику методу.

Наприклад:

let user = { name: "Іван", age: 30, sayHi() { // "this" -- це "поточний об’єкт" alert(this.name); } }; user.sayHi(); // Іван

Тут під час виконання коду user.sayHi(), значенням this буде user.

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

let user = { name: "Іван", age: 30, sayHi() { alert(user.name); // використовуємо змінну "user" замість "this" } };

…Але такий код ненадійний. Якщо ми вирішимо скопіювати user в іншу змінну, напр. admin = user перезаписати user чимось іншим, тоді цей код отримає доступ до неправильного об’єкта.

Це продемонстровано нижче:

let user = { name: "Іван", age: 30, sayHi() { alert( user.name ); // призводить до помилки } }; let admin = user; user = null; // перезапишемо значення змінної для наочності admin.sayHi(); // TypeError: Cannot read property 'name' of null

Якщо ми використовуємо this.name замість user.name всередині alert, тоді цей код буде працювати.

“this” не є фіксованим

В JavaScript, ключове слово this поводить себе не так, як в більшості мов програмування.

В цьому коді немає синтаксичної помилки:

function sayHi() { alert( this.name ); }

Значення this обчислюється під час виконання і залежить від контексту.

Наприклад, тут одна й та ж функція призначена двом різним об’єктам і має різний “this” при викликах:

let user = { name: "Іван" }; let admin = { name: "Адмін" }; function sayHi() { alert( this.name ); } // використовуємо одну і ту ж функцію у двох об’єктах user.f = sayHi; admin.f = sayHi; // виклики функцій, приведені нижче, мають різні this // "this" всередині функції є посиланням на об’єкт "перед крапкою" user.f(); // Іван (this == user) admin.f(); // Адмін (this == admin) admin['f'](); // Адмін (неважливо те, як звертатися до методу об’єкта -- через крапку чи квадратні дужки)

Правило просте: якщо obj.f() викликано, то this це obj під час виконання f. Так що в даному прикладі це user або admin.

Виклик без об’єкта: this == undefined

Ми можемо навіть викликати функцію взагалі без об’єкта:

function sayHi() { alert(this); } sayHi(); // undefined

В такому випадку this є undefined в суворому режимі ("use strict"). Якщо ми спробуємо звернутися до this.name трапиться помилка.

У несуворому режимі значенням this в такому випадку буде глобальний об’єкт (window у браузері, ми дійдемо до нього пізніше в главі Глобальний об’єкт). Це – поведінка, яка склалася історично та виправляється завдяки використанню суворого режиму ("use strict").

Зазвичай такий виклик є помилкою програмування. Якщо всередині функції є this, вона очікує виклику в контексті об’єкта.

Наслідки вільного this

Якщо ви прийшли з іншої мови програмування, то ви, мабуть, звикли до ідеї “зв’язаного this”, де методи, визначені в об’єкті, завжди мають this, що посилається на цей об’єкт.

В JavaScript this є “вільним”, його значення обчислюється під час виклику і не залежить від того, де метод був оголошений, а від того, який об’єкт “перед крапкою”.

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

Тут наша позиція полягає не в тому, щоб судити, добре чи погане таке рішення щодо дизайну мови. Ми зрозуміємо, як з цим працювати, як отримати переваги та уникнути проблем.

Стрілкові функції не мають “this”

Стрілкові функції особливі: у них немає “свого” this. Якщо ми посилаємось на this з такої функції, його значення береться із зовнішньої “нормальної” функції.

Наприклад, тут arrow() використовує this із зовнішнього user.sayHi() методу:

let user = { firstName: "Ілля", sayHi() { let arrow = () => alert(this.firstName); arrow(); } }; user.sayHi(); // Ілля

Це особливість стрілкових функцій є корисною коли ми не хочемо мати окреме this, а лише взяти його із зовнішнього контексту. Далі в главі Повторення стрілкових функцій ми детальніше розглянемо стрілкові функції.

Підсумки

  • Функції, які зберігаються у властивостях об’єкта, називаються “методами”.

  • Методи дозволяють об’єктам “діяти” подібно до object.doSomething().

  • Методи можуть посилатися на об’єкт завдяки this.

  • Значення this визначається під час виконання.

  • Коли функція оголошена, вона може використовувати this, але саме this не має значення, доки функція не буде викликана.

  • Функцію можна копіювати між об’єктами.

  • Коли функція викликається в синтаксисі “методу”: object.method(), значення this під час виклику є object – об’єкт перед крапкою.

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

Завдання

важливість: 5

Тут функція makeUser повертає об’єкт.

Який результат доступу до його поля ref? Чому?

function makeUser() { return { name: "Іван", ref: this }; } let user = makeUser(); alert( user.ref.name ); // Який результат?

Відповідь: помилка.

Спробуйте це:

function makeUser() { return { name: "Іван", ref: this }; } let user = makeUser(); alert( user.ref.name ); // Error: Cannot read property 'name' of undefined

Це тому, що правила, які встановлюють this, не розглядають оголошення об’єкта. Важливий лише момент виклику метода.

Тут значення this всередині makeUser() є undefined, оскільки воно викликається як функція, а не як метод із синтаксисом “через крапку”.

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

Отже, ref: this дійсно бере значення this функції.

Ми можемо переписати функцію і повернути те саме this зі значеннямundefined:

function makeUser(){ return this; // цього разу немає літерала об’єкта } alert( makeUser().name ); // Error: Cannot read property 'name' of undefined

Як бачите, результат alert( makeUser().name ) збігається з результатом alert( user.ref.name ) з попереднього прикладу.

Ось протилежний випадок:

function makeUser() { return { name: "Іван", ref() { return this; } }; } let user = makeUser(); alert( user.ref().name ); // Іван

Зараз це працює, оскільки user.ref() – це метод. І значення this встановлюється для об’єкта перед крапкою ..

важливість: 5

Створіть об’єкт calculator з трьома методами:

  • read() запитує два значення та зберігає їх як властивості об’єкта з іменами a та b відповідно.
  • sum() повертає суму збережених значень.
  • mul() множить збережені значення і повертає результат.
let calculator = { // ... ваш код ... }; calculator.read(); alert( calculator.sum() ); alert( calculator.mul() );

Запустити демонстрацію

Відкрити пісочницю з тестами.

let calculator = { sum() { return this.a + this.b; }, mul() { return this.a * this.b; }, read() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); } }; calculator.read(); alert( calculator.sum() ); alert( calculator.mul() );

Відкрити рішення із тестами в пісочниці.

важливість: 2

Існує об’єкт ladder, що дозволяє підійматися вгору-вниз:

let ladder = { step: 0, up() { this.step++; }, down() { this.step--; }, showStep: function() { // показує поточний крок alert( this.step ); } };

Тепер, якщо нам потрібно зробити кілька викликів послідовно, можна зробити це так:

ladder.up(); ladder.up(); ladder.down(); ladder.showStep(); // 1 ladder.down(); ladder.showStep(); // 0

Змініть код up, down і showStep так, щоб зробити доступним ланцюг викликів, наприклад:

ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0

Такий підхід широко використовується в бібліотеках JavaScript.

Відкрити пісочницю з тестами.

Рішення полягає у поверненні самого об’єкта з кожного виклику функції.

let ladder = { step: 0, up() { this.step++; return this; }, down() { this.step--; return this; }, showStep() { alert( this.step ); return this; } }; ladder.up().up().down().showStep().down().showStep(); // покаже 1, потім 0

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

ladder .up() .up() .down() .showStep() // 1 .down() .showStep(); // 0

Відкрити рішення із тестами в пісочниці.

Навчальна карта

Коментарі

прочитайте це, перш ніж коментувати…
  • Якщо у вас є пропозиції, щодо покращення підручника, будь ласка, створіть обговорення на GitHub або одразу створіть запит на злиття зі змінами.
  • Якщо ви не можете зрозуміти щось у статті, спробуйте покращити її, будь ласка.
  • Щоб вставити код, використовуйте тег <code>, для кількох рядків – обгорніть їх тегом <pre>, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)