Słowa kluczowe this, call, apply oraz bind są stosowane w celu wskazywania określonych elementów w kodzie JavaScript. Początkującym łatwo się w nich pogubić, przez co skrypt może działać niepoprawnie lub powodować błędy w przyszłości. Błędy te mogą popełniać nawet doświadczeni programiści. Metoda this w JavaScript to odwołanie do pewnego obiektu w kodzie. Jej zakres może być ograniczony przez cały skrypt lub tylko przez jego część, na przykład funkcję, tablicę itp. Metody call, apply i bind służą do wywoływania i przetwarzania wybranego elementu w kodzie. Dla wygody omówimy główne cechy wszystkich czterech metod oraz sytuacje, w których się je stosuje.

Niejawny kontekst w JavaScript

Kontekst wykonania w JavaScript to koncepcja opisująca środowisko, w którym wykonywany jest kod. Skrypt nie może działać bez jakiegoś kontekstu, ponieważ zawsze jest związany z czymś: innymi funkcjami, strukturą HTML/CSS, danymi użytkownika. W JS wyróżniamy trzy typy kontekstu wykonania:

Kluczowe słowo this może być niejawnie zdefiniowane w czterech kontekstach:

Przyjrzyjmy się teraz bliżej zastosowaniu this w tych kontekstach.

Kontekst globalny

W kontekście globalnym this odnosi się do globalnego obiektu – window w przypadku przeglądarki lub global w Node.js. Dla przykładu, jeśli wywołamy this w konsoli przeglądarki bez odwołania do konkretnego kontekstu, otrzymamy opis globalnego obiektu window. Wynika to z faktu, że this domyślnie odnosi się właśnie do niego.

Wynik wykonaniaWynik wykonania tego w kontekście globalnym bez konkretnego żądania w konsoli przeglądarki

Teraz spróbujmy zrobić to samo wewnątrz funkcji

W tym przypadku wykonanie polecenia nie powinno mieć miejsca w globalnym kontekście, więc this domyślnie powinno odnosić się do czegoś innego lub wywołać błąd. Napiszmy pustą funkcję i uruchommy ją w konsoli:

function printThis() {
  console.log(this);
}
printThis();
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

Jak widzisz, nawet wewnątrz funkcji this domyślnie odwołuje się do globalnego obiektu. Jednakże, jeśli wykonasz tę samą funkcję w trybie 'use strict', wartość this będzie undefined. Debugowanie kodu JavaScript w "trybie ścisłym" jest preferowane, ponieważ pomaga uniknąć błędów związanych z nieprawidłowymi odniesieniami.

Metoda wewnątrz obiektu

Nie należy mylić metody z funkcją, choć mają one podobne zastosowanie. Metoda zawiera definicje funkcji, które są zdefiniowane przez programistę lub użytkownika. Oto przykład metody:

const moscow = {
  name: 'Moscow',
  yearFounded: 1147,
  describe() {
    console.log(`${this.name} was founded in ${this.yearFounded}.`);
  },
}
moscow.describe();
// "Moscow was founded in 1147."

W tym przykładzie this odnosi się do obiektu moscow i wszystkich jego właściwości. This zawsze będzie odnosić się do elementu znajdującego się po lewej stronie kropki. Jeśli wartość nie zostanie zdefiniowana, w konsoli zostanie wyświetlona wartość undefined.

Konstruktor w funkcji lub klasie

Do tworzenia instancji funkcji lub klasy używa się słowa kluczowego new. Od 2015 roku słowo to jest coraz rzadziej używane, ponieważ pojawiła się aktualizacja składni ECMAScript w JavaScript. Oto przykład konstruktora funkcji z wykorzystaniem this:

function Country(name, yearFounded) {
  this.name = name;
  this.yearFounded = yearFounded;
  this.describe = function() {
    console.log(`${this.name} was founded in ${this.yearFounded}.`);
  };
}
const america = new Country('The United States of America', 1776);
america.describe();
// "The United States of America was founded in 1776."

Analogicznie, słowo kluczowe this działa również w konstruktorze klas.

DOM – obsługa zdarzeń

W przypadku obsługi zdarzeń this odnosi się do konkretnego zdarzenia, np. kliknięcia przycisku. Z tego powodu obowiązują nieco inne zasady niż przy odwołaniach do elementów, obiektów czy funkcji. Jeśli zdarzenie jest wywoływane za pomocą addEventListener, this domyślnie odnosi się do event.currentTarget. Należy jednak pamiętać, że event.target i event.currentTarget różnią się od siebie.

const button = document.createElement('button');
button.textContent = 'Click me';
document.body.append(button);
button.addEventListener('click', function(event) {
  console.log(this);
});

Po kliknięciu przycisku w konsoli zostanie wyświetlony obiekt odpowiadający temu przyciskowi.

Call, apply i bind

This jest odniesieniem do jakiejś części kodu. Jednak w przypadku konieczności odwoływania się do wielu komponentów, mogą pojawić się problemy. Do rozwiązywania tych sytuacji używane są metody call, apply i bind, które pozwalają określić, do czego this ma się odnosić.

Stosowanie metod call i apply

Omówimy je razem, ponieważ są bardzo podobne. Ich zadaniem jest wywołanie funkcji z określonym kontekstem this i dodatkowymi argumentami. Główna różnica polega na sposobie przekazywania argumentów: call przyjmuje argumenty pojedynczo, a apply przyjmuje je jako tablicę.

const book = {
  title: 'Brave New World',
  author: 'Aldous Huxley',
};
function summary() {
  console.log(`${this.title} was written by ${this.author}.`);
}
summary(); // undefined

funkcji w konsoli przeglądarkiWyświetlenie wyniku wykonania funkcji w konsoli przeglądarki

Przy próbie wykonania powyższego kodu w konsoli przeglądarki otrzymasz undefined zamiast oczekiwanego tekstu. Problem polega na tym, że bloki summary i book nie są ze sobą powiązane. Metoda this początkowo szuka odpowiednich elementów wewnątrz swojej funkcji (summary), a następnie globalnie. Jednakże, nie może „zaglądać” do innych bloków i pobierać z nich danych.

Rozwiązaniem tej sytuacji jest użycie metody call lub apply (w tym przypadku wybór metody nie ma znaczenia):

summary.call(book);
// lub:
summary.apply(book);

Wyświetlanie poprawnego wynikuWyświetlanie poprawnego wyniku w konsoli przeglądarki

Na koniec przyjrzyjmy się różnicom między metodami call i apply. Załóżmy, że musisz przekazać kilka argumentów do funkcji. Obie metody na to pozwalają, ale ich podejście różni się:

Dla call zapis wygląda tak: longerSummary.call(book, 'dystopian', 1932)

Dla apply będzie to: longerSummary.apply(book, ['dystopian', 1932])

Jeśli musisz przekazać wiele argumentów, to metoda apply będzie bardziej odpowiednia.

Metoda bind

Metody call i apply są metodami jednorazowego użytku, czyli pobierają dane z this, ale sama funkcja pozostaje niezmieniona. W dynamicznych aplikacjach webowych, takich jak gry przeglądarkowe, takie podejście nie jest zbyt wygodne, ponieważ użytkownik stale wprowadza nowe dane, a kod musi się szybko do nich dostosować. W takich sytuacjach używa się metody bind, która potrafi tworzyć nowe funkcje z jednoznacznie powiązanym this.

Przy każdym wywołaniu zmiennej braveNewWorldSummary zostanie zwrócona początkowa wartość this. W ten sposób zawsze otrzymasz oczekiwaną wartość this, co pozwala uniknąć błędów podczas wykonywania kodu. Dla przykładu spróbujmy powiązać nowy kontekst na podstawie funkcji wywołującej opisy książek w konsoli:

const braveNewWorldSummary = summary.bind(book);
braveNewWorldSummary(); // Brave New World was written by Aldous Huxley.
const book2 = {
  title: '1984',
  author: 'George Orwell',
};
braveNewWorldSummary.bind(book2);

Wynik wykonania funkcjiWynik wykonania funkcji w konsoli przeglądarki

Jak widać, nawet pomimo tego, że braveNewWorldSummary ma w argumencie book2, dane są pobierane z tego bloku, który był użyty w pierwotnym kodzie.

Funkcje strzałkowe

Omówione wcześniej metody call, apply i bind nie mają zastosowania do funkcji strzałkowych, ponieważ takie funkcje nie mają przypisanego kontekstu this. Zamiast tego od razu przechodzą do następnego poziomu wykonania. Co ciekawe, można używać this w takich funkcjach, ale będzie on działał nieco inaczej.

Oto przykład dwóch funkcji oraz wyników ich wykonania w konsoli:

const whoAmI = {
  name: 'Leslie Knope',
  regularFunction: function() {
    console.log(this.name);
  },
  arrowFunction: () => {
    console.log(this.name);
  },
};
whoAmI.regularFunction(); // "Leslie Knope"
whoAmI.arrowFunction(); // undefined

Jak widać, przy standardowym użyciu this funkcja strzałkowa zwróciła wartość undefined. Takie funkcje są zazwyczaj używane, gdy this ma odnosić się do zewnętrznego kodu. Na przykład, gdy mamy skrypt zdarzenia i funkcję opisującą akcję, ale wyzwalacz znajduje się poza tą funkcją, w ciele głównego skryptu. W takim przypadku lepiej użyć funkcji strzałkowej.

Podsumowanie

Początkujący programista może mieć trudności z odróżnieniem this, call, apply i bind. Wszystkie te metody odnoszą się do komponentu kodu, ale robią to na różne sposoby. Metoda this służy do prostych odniesień, call i apply pozwalają na przekazywanie argumentów, a bind ustala początkową wartość. Nie można też zapominać o funkcjach strzałkowych, które nie korzystają z call, apply i bind, a this odnosi się jedynie do zewnętrznego kodu. Po opanowaniu różnic między tymi czterema metodami będzie można tworzyć skomplikowane, ale poprawnie działające skrypty w JavaScript.