Функції стрілки та `this` у JavaScript: повний посібник

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

Ілюстрація стрілки та цієї функції в JavaScript

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

У цій статті ми детальніше розглянемо, як це працює Це стосується традиційних функцій та стрілкових функцій.Чому іноді здається, що він вказує на певний об'єкт, а іноді на глобальний об'єкт, і в яких ситуаціях має сенс використовувати стрілкові функції, а в яких краще їх уникати?

Що саме this в JavaScript

Заповідне слово this Це посилання на контекст виконання функції, яка наразі виконується. На відміну від інших мов програмування, у JavaScript рішення ґрунтується не на тому, де визначено функцію, а на її виконанні. як викликати.

Це означає, що одну й ту саму функцію можна викликати різними способами, і в кожному з них, this може вказувати на інший об'єктЦе не те, що можна змінити прямим завданням (ви не можете це зробити this = algo), але ви можете впливати на це за допомогою певних механізмів, таких як call, apply y bind.

Крім того, їхня поведінка варіюється між суворий режим та несуворий режимУ нестрогому режимі, якщо викликати функцію "голою" (без об'єкта перед нею), this Зазвичай це глобальний об'єкт (у браузері, window), тоді як у суворому режимі це може бути undefinedЦя відмінність важлива під час порівняння прикладів коду з різних джерел.

Це в глобальному контексті та в звичайних функціях

У браузерах, коли ви не перебуваєте всередині жодного модуля чи функції, глобальним контекстом є об'єкт window, і там this вказати на цей об'єктТобто, якщо ви введете в консоль наступне:

console.log(this === window); // true en un entorno de navegador no estricto

У функції, оголошеній «класичним» способом (звичайна функція), значення this Це залежить від того, як називається ця функція.Якщо викликати його без попереднього об'єкта, у несуворому режимі this Зазвичай це глобальний, і, строго кажучи, це буде undefinedОсь чому іноді, під час переміщення коду з одного сайту на інший, Це вже не те, чого ви очікували..

Це в методах об'єктів, визначених за допомогою звичайних функцій

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

Наприклад, якщо у вас є щось на кшталт:

const obj = {
  speak() {
    console.log(this);
  }
};
obj.speak();

Дзвінок obj.speak() робить this в speak будь тим єдиним objЦе поведінка, яку люди зазвичай очікують інтуїтивно: метод говорить «від імені» об'єкта.

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

Це в методах, визначених за допомогою стрілкових функцій

Ситуація змінюється, коли ви визначаєте метод за допомогою стрілкової функції. Щось на кшталт:

const obj2 = {
  speak: () => {
    console.log(this);
  }
};
obj2.speak();

У цьому випадку, під час виконання obj2.speak() ти це побачиш this його вже немає obj2але зовнішній лексичний контекст до цього об'єкта, який у класичному скрипті браузера зазвичай є глобальним об'єктом window.

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

Тому часто повторювана рекомендація в сучасній документації полягає в наступному: Не використовуйте стрілкові функції як методи об'єкта коли тобі потрібно this прицільтеся до цього об'єкта.

Лексичний обсяг this функції зі стрілками

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

Уявіть собі такий приклад:

const obj3 = {
  speak() {
    (() => {
      console.log(this);
    })();
  }
};
obj3.speak();

Тут може здатися, що, як і всередині speak ми виконуємо стрілкову функцію, Це має "скинутися" до глобальногоАле відбувається якраз навпаки: стрілочна функція фіксує this функції, що її оточуєщо в цьому випадку є методом speak викликається як obj3.speak()Отже, значення this Той, що показано на консолі, — це той, що з obj3.

Я маю на увазі, функції стрілок не мають власних thisа радше використовують те, що знаходиться у їхньому безпосередньому оточенніЦе неймовірно корисно у вкладених зворотних викликах, таймерах, промісах та будь-де, де з класичними функціями доводилося мати справу з... .bind або за допомогою таких хитрощів, як const that = this;.

Практичні приклади втрати та збереження this

Одна з класичних проблем у JavaScript полягає в тому, що під час визначення функції всередині методу, ви втрачаєте посилання на this що вказував на об'єкт і ви отримаєте глобальний варіант або undefined.

Візьмемо типовий випадок використання setTimeout у методі об'єкта з традиційною функцією:

const persona = {
  nombre: 'Agustin',
  decirNombre: function() {
    setTimeout(function() {
      console.log(this.nombre);
    }, 3000);
  }
};
persona.decirNombre(); // Muestra undefined

тут це всередині функції, переданої до setTimeout Це вже не об'єкт personaЦя функція зворотного виклику виконується в глобальному контексті (у браузері, window), тому this.nombre Він намагається прочитати властивість у глобальному переменному, якої не існує, і в результаті отримує undefined.

До появи стрілкових функцій поширеним рішенням було зберігання значення this у допоміжній змінній щоб "перетягнути" його у функцію:

const persona = {
  nombre: 'Agustin',
  decirNombre: function() {
    let that = this; // aquí this es persona
    setTimeout(function() {
      console.log(that.nombre);
    }, 3000);
  }
};

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

const persona = {
  nombre: 'Agustin',
  decirNombre: function() {
    setTimeout(() => {
      console.log(this.nombre);
    }, 3000);
  }
};

Тут функція стрілки не створює власних thisтак успадковує this методу decirNombreякий є об'єктом personaРезультат: «Агустін» відображається правильно без потреби в проміжних змінних або .bind.

виклик, застосування та прив’язка: керування значенням this

Окрім «природного» способу встановлення контексту за допомогою виклику методу, JavaScript надає нам інструменти для примусово встановити значення this у нормальних функціях: call, apply y bind.

Методи call() y apply() Вони викликають функцію негайно, дозволяючи вам передавати об'єкт, який ви хочете використовувати як this. Різниця в тому call отримує аргументи один за одним, тоді як apply Вони отримуються в масиві. bind(), натомість вона повертає нову функцію з this «додається» до вказаного вами значеннятож ти зможеш зателефонувати їй пізніше, коли тобі буде зручно.

Однак, зі стрілковими функціями ці методи не корисні для зміни this оскільки його значення лексично пов'язане. Можна використовувати call, apply o bind передавати аргументи, але не змінювати контекст стрілкових функцій, що є дуже важливою відмінністю від звичайних функцій.

Базовий синтаксис стрілкових функцій

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

(arg1, arg2, ..., argN) => expresion

Ця форма автоматично повертає результат виразу праворуч від стрілки, тому Немає потреби писати це слово return коли у вас є лише один простий вираз.

Деякі спільні моменти синтаксису:

  • Без параметрів:
    () => 42 про Включена _ => 42 якщо вам байдуже ім'я аргументу.
  • З одним параметром:
    Дужки необов'язкові; ви можете написати x => x * 2 o (x) => x * 2.
  • З кількома параметрами:
    Дужки обов'язкові: (x, y) => x + y.

Коли вам потрібно кілька операторів, ви можете використовувати тіло блоку з ключами:

const sumar = (x, y) => {
  const resultado = x + y;
  return resultado;
};

У цьому випадку, оскільки є ключі, Більше немає жодного неявного повернення; якщо ви не покладете returnфункція поверне undefinedЦе стосується як стрілкових функцій, так і традиційних функцій.

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

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

Наприклад:

x => ({ y: x })

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

Стрілочні функції: анонімні та без прототипу

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

Крім того, функції стрілок Вони не володіють майном prototype і їх не можна використовувати як будівельні компаніїЯкщо ви спробуєте викликати їх за допомогою newВи отримаєте помилку. Щоб створювати об'єкти за допомогою конструкторів або класів, вам все одно потрібно використовувати звичайні функції або синтаксис. class.

Ще одним наслідком є ​​те, що Вони не підходять для шаблонів, що потребують внутрішнього самопосилання., наприклад, деякі форми рекурсії або обробники подій, яким потрібно відписатися за допомогою this або власну назву функції.

Де сяють функції стрілок

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

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

const contador = {
  id: 42,
  iniciar() {
    setTimeout(() => {
      console.log(this.id); // this es contador
    }, 1000);
  }
};

У ES5 було звичайною справою доводилося ставити .bind(this) до зворотного виклику або збереження this в іншій змінній. Зі стрілковими функціями, Код стає чистішим і ближчим до фактичного наміру..

Вони також дуже практичні з методами масивів, такими як map, filter, reduce і компанія, тому що зменшити синтаксичний шум коли логіка функції коротка:

const numeros = [1, 2, 3];
const dobles = numeros.map(n => n * 2);

За умов економного використання ці компактні форми полегшують відстеження потоку даних з першого погляду.

Коли слід уникати стрілкових функцій

Хоча стрілкові функції дуже корисні, вони не замінюють звичайні функції. Існує кілька очевидних випадків, коли Найкраще їх не використовувати.:

  • Методи об'єктів, що залежать від this:
    Якщо ви визначаєте метод як saltos: () => { this.vidas--; } всередині об'єкта gato, this Він вказуватиме не на кота, а на зовнішнє середовище, і властивість не оновлюватиметься, як ви очікуєте.
  • Зворотні виклики подій DOM, яким потрібен this динамічний:
    У обробнику, такому як boton.addEventListener('click', () => { this.classList.toggle('on'); });, this Це буде не натиснута кнопка, а вищий контекст, який, ймовірно, видасть вам помилку типу.
  • Конструктори або функції, які потребують prototype:
    Оскільки його не можна використовувати з newСтрілкові функції не підходять для створення екземплярів або для шаблонів на основі прототипів.

У всіх цих випадках, a Нормальна функція залишається відповідним інструментом тому що це дозволяє this Це динамічно пов'язано зі способом виклику функції.

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

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