This. Call vs apply vs bind. Arrow Functions i this

W tym artykule opiszę czym jest this, jak się ma do tego i czym się różni call, apply oraz bind. Wrócimy także do postów o arrow functions, gdzie należałoby bardziej rozwinąć jak zachowuje się w ich przypadku this.

Artykuł o arrow function można znaleźć: tutaj.

# Czym jest this?

this to specjalny keyword, który znajduje się w każdej funkcji, przechowujący wartość zależną od kontekstu w jakim została wywołana.

Zobaczmy jak to wygląda w praktyce:

 
function foo() {
	console.log( this.a );
}
 
var a = 2; //globalne a
 
foo(); // 2 
 
var obj = {
	a: 42, //lokalne a
	foo: foo
};
 
obj.foo() //42

funkcja foo() wywołana w zasięgu globalnym przyjmuje wartość this tego zasięgu, więc zmienna a, którą znajduje to globalna zmienna a = 2. W przypadku, którym stworzymy obiekt ze swoja wartością a oraz dodamy funkcję foo wartość this będzie inna. Funkcja wywołana w kontekście obiektu obj z przykladu ustawia wartość this na zasięg lokalny obiektu, dlatego zwraca lokalną wartość a, czyli 42.

Zgodnie z sugestią czytelnika, dodam jeszcze ciekawostkę dotyczącą trybu strict, który tłumaczyłem w jednym z wcześniejszych postów: Co to jest strict mode?

 
"use strict";
function foo() {
	console.log( this.a );
}
 
var a = 2; //globalne a
 
foo(); //TypeError: Cannot read property 'a' of undefined

W tym przypadku funkcja foo ma ustawioną wartość this na zasięg globalny, jednak tryb strict nie pozwala na odniesienie się do this jako obiektu globalnego i dlatego otrzymujemy błąd.

# Apply i Call

Każda z tych funkcji wywołuje metodę z ustawionym this. Różnią się tym, że Apply przyjmuje argumenty jako tablicę, a Call przyjmuje argumenty wypisane po przecinku. Dodatkowo Call jest szybsze w wykonaniu.

Stwórzmy sobie obiekt, w kontekście którego wywoływać będziemy funkcję z keywordem this:

 
var band = {name: "ACDC"};
 
var tour = function(target, target2){
	console.log(`${this.name} tour countries: ${target} and ${target2}.`);
}
 
tour("Poland", "France"); //undefined tour countries: Poland and France.
tour.call(band, "Poland", "France"); //ACDC tour countries: Poland and France.
tour.apply(band, ["Poland", "France"]) //ACDC tour countries: Poland and France.

We funkcji tour skorzystaliśmy z uproszczonego zapisu dzięki ES6 `${this.name} tour countries: ${target} and ${target2}.`. Wyświetlamy w nim zmienną zależną od this oraz dwa podane argumenty.

Na początek wywołujemy funkcję tour, która nie znajduje w zasięgu globalnym zmiennej name. Następnie ustawiamy kontekst funkcji tour na obiekt band za pomocą call i podajemy argumenty po przecinku. Podobnie jak poprzednio używamy funkcji apply, z tą różnicą, że argumenty przekazujemy jako tablica.

# Bind

bind działa bardzo podobnie jak call i apply, z tą różnicą, że zwraca nową funkcję z ustawionym this.

Wróćmy do poprzedniego przykładu, który może być troszkę zaskakujący:

 
function foo() {
	console.log( this.a );
}
 
var a = 2; //globalne a
 
var obj = {
	a: 42, //lokalne a
	foo: foo
};
 
const butWhy = obj.foo;
butWhy(); //2

Dlaczego tym razem funkcja butWhy() zwróciła globalne a, zamiast lokalnego, mimo że przypisujemy do niej lokalne foo? Przypisując do nowej zmiennej funkcję z obiektu obj, tak naprawdę przypisaliśmy tą funkcję, ale bez kontekstu. W momencie, kiedy wywołujemy nową zmienną butWhy() funkcja jest wywołana poza obiektem i ma zasięg globalny. Żeby to naprawić należy użyć bind:

 
const letsBind = obj.foo.bind(obj);
letsBind(); //42

Stworzyliśmy zmienną letsBind zawierającą funkcję foo oraz nadaliśmy jej kontekst obiektu obj, dzieki czemu nasze this.a wskazuje na lokalną zmienną a.

# Arrow Functions i this

Funkcje o których pisaliśmy już wcześniej, przy okazji serii dotyczącej ES6: tutaj, oferują nie tylko skrócony syntax. W przypadku arrow functions wartość this zachowuje się trochę inaczej niż normalnie. Funkcja strzałkowa ignoruje wszystkie zasady this oraz apply, call i bind. Kontekst this jest zawsze otaczającym zasięgiem z momentu utworzenia tej funkcji zamiast wywołania.

Wróćmy do poprzedniego przykładu, z tą różnicą, że użyjemy funkcji strzałkowej:

 
foo = () => console.log(this.a);
 
var a = 2; //globalne a
 
var obj = {
	a: 42, //lokalne a
	foo: foo
};
 
obj.foo();

const letsBind = obj.foo.bind(obj);
letsBind();

Funkcja foo posiada wartość this ustawioną na tą z momentu utworzenia funkcji. Zarówno wywołanie na obiekcie jak i użycie bind, które działały w przypadku zwykłej funkcji, nie zmieniają kontekstu this. Za każdym razem funkcja wywołuje globalną zmienną a = 2.

Kolejny przykład który świetnie obrazuje arrow functions i this, to przykład z użyciem map, o którym więcej można dowiedzieć się w jednym z poprzednich postów dotyczących programowania funkcyjnego: Czym jest Map, a czym jest Filter?

 
const number = {
    num: 5
}
let arr = [1,2,3];

arr.map(n => this.num * n, number); //[NaN, NaN, NaN]

arr.map(function (n) {
    return this.num * n;
}, number); //[5,10,15]

Map, jako drugi argument może przyjmować wartość, którą ma użyć jako this podczas wywołania. W tym przypadku wksazujemy na obiekt number i odwołujemy się do this.num. Jednak przy użyciu funkcji strzałkowej, nie ma możliwości nadania wartości this, która zawsze przyjmuje zasięg z momentu stworzenia tej funkcji. Jak możemy zaobserwować, w przypadku użycia normalnej funkcji, this przyjmuje taką wartość, jaką podaliśmy w map.

Published: July 16 2017

blog comments powered by Disqus