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:
- Kontekst globalny: Dotyczy kodu znajdującego się poza jakąkolwiek funkcją. Charakteryzuje się obecnością globalnego obiektu. Domyślnie jest to window w przeglądarce. W programie może istnieć tylko jeden globalny kontekst.
- Kontekst funkcji: Dla każdej nowej funkcji w kodzie tworzony jest osobny kontekst. W przeciwieństwie do kontekstu globalnego, liczba kontekstów funkcji jest nieograniczona.
- Kontekst funkcji eval: Jest to kontekst wewnątrz funkcji eval. Jest rzadko używany, więc nie będziemy go szczegółowo omawiać.
Kluczowe słowo this może być niejawnie zdefiniowane w czterech kontekstach:
- Kontekst globalny;
- Metoda wewnątrz obiektu;
- Konstruktor w funkcji lub klasie;
- Obsługa zdarzeń DOM.
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.
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
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);
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);
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.