
call() i apply() wykonują funkcję natychmiast z wybranym this; call dostaje argumenty pojedynczo, apply w tablicy. bind() nie wykonuje niczego: zwraca nową funkcję, w której this (a czasem też pierwsze argumenty) są ustalone na stałe. Sięgamy po nie wtedy, gdy metoda trafia do callbacka i gubi swój obiekt.
Każda funkcja w JavaScripcie ma trzy metody — call(), apply() i bind() — które służą temu samemu: decydują, czym jest this w chwili wykonania funkcji. Żeby zrozumieć, po co są, trzeba najpierw poznać problem, który rozwiązują: w JavaScripcie this nie zależy od miejsca, w którym funkcja została napisana, tylko od sposobu, w jaki została wywołana.
Problem: this zmienia się zależnie od wywołania
Cztery reguły wystarczą, żeby przewidzieć this w niemal każdej sytuacji:
- Wywołanie metody
objet.fonction():thistoobjet. - Zwykłe wywołanie
fonction():thistoundefinedw trybie strict (moduły, klasy), a poza nim obiekt globalnywindow. - Wywołanie z
new:thisto właśnie konstruowany obiekt. - Wywołanie przez
call,applyalbo funkcję związaną przezbind:thisjest tym, co sam wskazałeś.
Funkcje strzałkowe wymykają się tym regułom: nie mają własnego this i zachowują ten z otaczającego kodu.
W praktyce boli przejście od reguły 1 do reguły 2:
const person = {
name: 'Sarah',
hello() {
return `Bonjour ${this.name}`;
},
};
person.hello(); // 'Bonjour Sarah' : wywołanie metody, this = person
const detached = person.hello;
detached(); // TypeError w trybie strict: this jest undefined
// ('Bonjour undefined' albo 'Bonjour ' poza trybem strict)Funkcja się nie zmieniła. Zmienił się tylko sposób jej wywołania, a razem z nim this. Dokładnie to dzieje się, gdy metodę przekazujemy do setTimeout, addEventListener albo map.
call(): wywołanie z wybranym this
fonction.call(thisArg, arg1, arg2, …) wykonuje funkcję natychmiast, z this równym thisArg i argumentami podanymi pojedynczo.
function presenter(age, ville) {
return `${this.name}, ${age} ans, ${ville}`;
}
const person = { name: 'Sarah' };
presenter.call(person, 30, 'Lille'); // 'Sarah, 30 ans, Lille'
presenter.call({ name: 'Léa' }, 25, 'Paris'); // 'Léa, 25 ans, Paris'
const detached = person.hello; // metoda z poprzedniego przykładu
detached.call(person); // 'Bonjour Sarah' : this przywróconyKlasyczne zastosowanie: użycie metody tablicy na czymś, co tablicą nie jest.
Array.prototype.slice.call('abc'); // ['a', 'b', 'c']
// Dziś napisalibyśmy raczej:
Array.from('abc'); // ['a', 'b', 'c']apply(): to samo, argumenty w tablicy
fonction.apply(thisArg, [arg1, arg2, …]) robi dokładnie to, co call, ale argumenty dostaje w tablicy. Wygodne, gdy i tak przychodzą zgrupowane.
presenter.apply(person, [30, 'Lille']); // 'Sarah, 30 ans, Lille'
const notes = [3, 9, 4];
Math.max.apply(null, notes); // 9 : Math.max oczekuje osobnych argumentów, nie tablicyOd ES2015 operator spread sprawia, że apply rzadko bywa potrzebny: Math.max(...notes) i presenter.call(person, ...args) robią to samo i pozostają czytelne. apply spotyka się głównie w starszym kodzie.
bind(): ustawienie this na później
Tam gdzie call i apply wykonują funkcję od razu, bind nie wykonuje niczego. Zwraca nową funkcję, w której this jest ustawiony na stałe. To narzędzie do callbacków: funkcję przygotowujesz teraz, a wywoła ją później ktoś inny.
const bound = person.hello.bind(person);
bound(); // 'Bonjour Sarah', niezależnie od kontekstu wywołania
bound.call({ name: 'X' }); // 'Bonjour Sarah' : bind wygrywa, call nie zmieni już this
bound.name; // 'bound hello'Typowy przypadek: metoda przekazana jako callback
class Compteur {
constructor() {
this.n = 0;
}
incrementer() {
this.n += 1;
return this.n;
}
}
const compteur = new Compteur();
// Bez bind: metoda jest oderwana od swojego obiektu
const inc = compteur.incrementer;
inc(); // TypeError: Cannot read properties of undefined (reading 'n')
// Z bind: this jest ustawiony na compteur
const incLie = compteur.incrementer.bind(compteur);
incLie(); // 1
incLie(); // 2Ten sam schemat dotyczy timerów i zdarzeń w przeglądarce:
setTimeout(compteur.incrementer.bind(compteur), 1000);
bouton.addEventListener('click', compteur.incrementer.bind(compteur));
// Żeby móc później usunąć nasłuchiwacz, zachowaj związaną referencję:
const onClick = compteur.incrementer.bind(compteur);
bouton.addEventListener('click', onClick);
bouton.removeEventListener('click', onClick);Bez bind funkcja addEventListener wywołałaby incrementer z this równym klikniętemu przyciskowi: this.n byłoby undefined, a licznik nie policzyłby niczego.
bind() do aplikacji częściowej
Argumenty podane do bind po this również zostają ustalone. Zwrócona funkcja czeka na kolejne.
const presenterSarah = presenter.bind(person, 30);
presenterSarah('Lille'); // 'Sarah, 30 ans, Lille' : do podania zostaje tylko ostatni argument
const add = (a, b) => a + b;
const add5 = add.bind(null, 5); // this nie jest potrzebny: wystarczy null
add5(3); // 8To prosty sposób na wyspecjalizowanie ogólnej funkcji bez pisania funkcji opakowującej.
Funkcje strzałkowe: kiedy bind staje się zbędny
Funkcja strzałkowa przechwytuje this z miejsca, w którym została napisana, i później go ignoruje. Stąd dwa wnioski.
Po pierwsze, call, apply i bind nie zmienią jej this:
const arrow = () => this;
arrow.call({ a: 1 }); // this modułu, a nie { a: 1 }Po drugie, i to jest jej codzienne zastosowanie, strzałkowa zadeklarowana w metodzie albo w klasie zachowuje właściwy this bez bind:
class Compteur {
n = 0;
// Właściwość strzałkowa: this przechwycony raz na zawsze
incrementer = () => {
this.n += 1;
return this.n;
};
}
const c = new Compteur();
const inc = c.incrementer;
inc(); // 1 : działa bez bind
setTimeout(() => c.incrementer(), 1000); // albo funkcja strzałkowa przy wywołaniuDruga strona medalu: właściwość strzałkowa powstaje na nowo w każdej instancji, podczas gdy zwykła metoda jest współdzielona przez prototyp. Przy kilku obiektach nie ma to znaczenia; przy tysiącach instancji metoda związana raz w konstruktorze (this.incrementer = this.incrementer.bind(this)) oszczędza pamięć.
this podany jako drugi argument
Kilka metod tablicowych przyjmuje wprost wartość this do użycia w callbacku, co pozwala pominąć bind:
const config = { k: 10 };
[1, 2, 3].map(function (x) { return this.k * x; }, config); // [10, 20, 30]forEach, filter, some i every mają ten sam drugi argument. Przy funkcji strzałkowej staje się on bezużyteczny.
Tabela porównawcza
call | apply | bind | |
|---|---|---|---|
| Wykonuje funkcję | natychmiast | natychmiast | nie: zwraca funkcję |
| Argumenty | pojedynczo | w tablicy | pojedynczo, ustalone dla przyszłych wywołań |
| Typowe użycie | pożyczenie metody | rozwinięcie tablicy (zastąpione przez ...) | callbacki, zdarzenia, aplikacja częściowa |
| Wpływ na funkcję strzałkową | żaden na this | żaden na this | żaden na this, ustalane są argumenty |
Częste błędy i jak je czytać
- „Cannot read properties of undefined (reading 'n’)” w metodzie klasy: metoda została oderwana od swojej instancji (reguła 2). Zwiąż ją przez
bindalbo zadeklaruj jako właściwość strzałkową. - Nasłuchiwacz zdarzenia, który nie zmienia stanu:
thisto element DOM, a nie twój obiekt. Lekarstwo to samo. - Wywołanie
bind, które zdaje się nic nie robić: funkcja jest strzałkowa. Usuńbind, jest zbędny. removeEventListener, który niczego nie usuwa: każdebindtworzy nową funkcję; przy dodawaniu i przy usuwaniu trzeba podać tę samą związaną referencję.
Dalej: klasy w JavaScripcie wracają do metod i prototypu, a rozdział Funkcje z kursu daje podstawy, jeśli słownictwo z tego artykułu jest jeszcze nowe. W PHP sprawa wygląda inaczej: $this nigdy nie zmienia obiektu.
Częste błędy
bouton.addEventListener('click', compteur.incrementer) wywołuje funkcję z this równym przyciskowi, a nie compteur. Napisz compteur.incrementer.bind(compteur) albo funkcję strzałkową.this: call, apply i bind go nie zmienią. Liczą się tylko argumenty podane przez bind.bind tworzy nową funkcję przy każdym wywołaniu. W pętli albo przy powtarzanym renderowaniu zwiąż metodę raz w konstruktorze (albo zadeklaruj ją jako właściwość strzałkową).f.call(obj, [1, 2]) przekazuje jeden argument, czyli tablicę. Do rozwinięcia tablicy służy apply albo f.call(obj, ...tableau).

