Navody

Vazba kontextu na funkci

Při předávání metod objektu jako zpětných volání, například pro setTimeout, dochází k dobře známému problému: toto se ztratí.

V této kapitole se podíváme, jak to lze vyřešit.

Ztráta „tohoto“

Už jsme viděli příklady, kdy se to ztratilo. Jakmile je metoda předána odděleně od objektu, dojde ke ztrátě.

Zde je návod, jak by se to mohlo stát s setTimeout :

let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); >>; setTimeout(user.sayHi, 1000); // Привет, undefined!

Když spustíme tento kód, vidíme, že volání this.firstName vrací ne „Vasya“, ale undefined !

Stalo se to proto, že setTimeout přijal funkci sayHi odděleně od objektu user (zde funkce ztratila kontext). Poslední řádek lze tedy přepsat jako:

let f = user.sayHi; setTimeout(f, 1000); // контекст user потеряли

Metoda setTimeout v prohlížeči má speciální funkci: nastaví this=window na volání funkce (v Node.js se to stane objektem časovače, ale to zde nehraje roli). Takže pro this.firstName se pokusí získat window.firstName, který neexistuje. V jiných podobných případech se this obvykle stane undefined.

Úloha je docela typická – chceme předat metodu objektu někam jinam (v tomto konkrétním případě – plánovači), kde bude volána. Jak to udělat tak, aby byla volána ve správném kontextu?

Řešení 1: Vytvořte obalovací funkci

Nejjednodušším řešením je zabalit volání do anonymní funkce a vytvořit tak uzávěr:

let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); >>; setTimeout(function() < user.sayHi(); // Привет, Вася! >, 1000);

Nyní kód funguje správně, protože objekt uživatele je načten z uzávěru a poté je volána jeho metoda sayHi.

Totéž, jen kratší:

setTimeout(() => user.sayHi(), 1000); // Привет, Вася!

Vypadá to dobře, ale teď máme v našem kódu malou zranitelnost.

Co se stane, když se do uživatelské proměnné zapíše jiná hodnota před spuštěním funkce setTimeout (zpoždění je celá sekunda!)? Volání pak bude najednou úplně jiné!

let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); >>; setTimeout(() => user.sayHi(), 1000); // . в течение 1 секунды user = < sayHi() < alert("Другой пользователь в 'setTimeout'!"); >>; // Другой пользователь в 'setTimeout'!

Následující řešení zajistí, že se to nestane.

Řešení 2: Svázat kontext pomocí příkazu bind

V moderním JavaScriptu mají funkce vestavěnou metodu bind, která umožňuje toto uzamknout.

Základní syntaxe vazby:

// полный синтаксис будет представлен немного позже let boundFunc = func.bind(context);

Výsledkem volání func.bind(context) je speciální „exotický objekt“ (termín je převzat ze specifikace), který je volán jako funkce a transparentně předává volání funkci func , přičemž nastavuje this=context .

Jinými slovy, volání boundFunc je jako volání func s pevně danou hodnotou this.

Například zde funcUser předává volání funkci func a opravuje tak hodnotu this=user:

let user = < firstName: "Вася" >; function func() < alert(this.firstName); >let funcUser = func.bind(user); funcUser(); // Вася

Zde je func.bind(user) „vázaná verze“ funkce func s pevně danou hodnotou this=user.

Přečtěte si více
Nejlepší plemena dojnic - fotografie a popisy

Všechny argumenty jsou předávány původní metodě func tak, jak jsou, například:

let user = < firstName: "Вася" >; function func(phrase) < alert(phrase + ', ' + this.firstName); >// привязка this к user let funcUser = func.bind(user); funcUser("Привет"); // Привет, Вася (аргумент "Привет" передан, при этом this = user)

Nyní to zkusme s objektovou metodou:

let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); >>; let sayHi = user.sayHi.bind(user); // (*) sayHi(); // Привет, Вася! setTimeout(sayHi, 1000); // Привет, Вася!

Na řádku (*) vezmeme metodu user.sayHi a navážeme ji na user. sayHi je nyní „vázaná“ funkce, kterou lze volat samostatně nebo předat funkci setTimeout (kontext bude vždy správný).

Zde vidíme, že příkaz bind opravuje pouze toto a argumenty se předávají tak, jak jsou:

let user = < firstName: "Вася", say(phrase) < alert(`$, $!`); >>; let say = user.say.bind(user); say("Привет"); // Привет, Вася (аргумент "Привет" передан в функцию "say") say("Пока"); // Пока, Вася (аргумент "Пока" передан в функцию "say")

Pohodlná metoda: bindAll

Pokud má objekt mnoho metod a plánujeme je aktivně předávat, můžeme pro všechny z nich vytvořit smyčku:

for (let key in user) < if (typeof user[key] == 'function') < user[key] = user[key].bind(user); >>

Některé JS knihovny poskytují vestavěné funkce pro pohodlné hromadné vázání kontextu, například _.bindAll(obj) v lodashu.

Částečná aplikace

Zatím jsme mluvili pouze o vazbě tohoto. Pojďme to udělat o krok dál.

Můžeme svázat nejen toto, ale i argumenty. To se dělá zřídka, ale někdy to může být užitečné.

Úplná syntaxe příkazu bind je:

let bound = func.bind(context, [arg1], [arg2], . );

To umožňuje propojit kontext this a počáteční argumenty funkce.

Například máme multiplikační funkci mul(a, b):

funkce mul(a, b)

Použijme bind k vytvoření funkce double založené na něm:

function mul(a, b) < return a * b; >let double = mul.bind(null, 2); alert( double(3) ); // = mul(2, 3) = 6 alert( double(4) ); // = mul(2, 4) = 8 alert( double(5) ); // = mul(2, 5) = 10

Volání mul.bind(null, 2) vytvoří novou funkci double, která předává volání funkci mul, přičemž zachycuje null jako kontext a 2 jako první argument. Následující argumenty se předávají tak, jak jsou.

Tomu se říká částečná aplikace – novou funkci vytvoříme opravou některých stávajících parametrů.

V tomto případě vlastně nepoužíváme this. Pro bind je to ale povinný parametr, takže bychom tam měli předat něco jako null.

V následujícím kódu funkce triple vynásobí hodnotu třemi:

function mul(a, b) < return a * b; >let triple = mul.bind(null, 3); alert( triple(3) ); // = mul(3, 3) = 9 alert( triple(4) ); // = mul(3, 4) = 12 alert( triple(5) ); // = mul(3, 5) = 15

Proč obvykle vytváříme částečně aplikovanou funkci?

Výhodou je, že je možné vytvořit nezávislou funkci s jasným názvem (double, triple). Můžeme ji použít a nemusíme pokaždé předávat první argument, protože je pevně stanoven pomocí bind.

Přečtěte si více
Plástový med: léčivé vlastnosti, recepty na léčbu plástvového medu, koupit plástový med, doručení, slevy

V jiných případech je částečná aplikace užitečná, když máme velmi obecnou funkci a pro pohodlí chceme vytvořit její specializovanější verzi.

Například máme funkci send(from, to, text). Pak bychom uvnitř objektu user mohli chtít použít její privátní verzi: sendTo(to, text) , která odešle text jménem aktuálního uživatele.

Částečná aplikace bez kontextu

Co když chceme zachytit některé argumenty, ale ne kontext this? Například pro metodu objektu.

Vestavěná funkce bind to neumožňuje. Nemůžeme jen tak přeskočit kontext a přejít k argumentům.

Naštěstí je snadné vytvořit částečnou pomocnou funkci, která váže pouze argumenty.

function partial(func, . argsBound) < return function(. args) < // (*) return func.call(this, . argsBound, . args); >> // использование: let user = < firstName: "John", say(time, phrase) < alert(`[$] $: $!`); >>; // добавляем частично применённый метод с фиксированным временем user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); user.sayNow("Hello"); // Что-то вроде этого: // [10:00] John: Hello!

Výsledkem volání partial(func[, arg1, arg2. ]) je wrapper (*), který volá func s:

  • Stejné this, které obdrží (pro volání user.sayNow to bude user )
  • Pak ho předá. argsBound – argumenty z částečného volání («10:00» )
  • Pak předá . args – argumenty přijaté wrapperem („Hello“)

Díky operátoru spread je to velmi snadné implementovat, že?

Existuje také hotová verze _.partial z knihovny lodash.

Celkem

Metoda bind vrací „vázanou verzi“ funkce func, která zachycuje kontext this a první argumenty arg1, arg2 . pokud jsou zadány.

Metoda bind se obvykle používá k zachycení této hodnoty v objektové metodě a jejímu předání jako zpětné volání. Například pro setTimeout.

Když vážeme argumenty, taková funkce se nazývá „částečně aplikovaná“ nebo „parciální“.

Částečná aplikace je výhodná, když nechceme opakovat stejný argument mnohokrát. Například, pokud máme funkci send(from, to) a from bude pro naši úlohu vždy stejné, pak můžeme vytvořit částečně aplikovanou funkci a dále s ní pracovat.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

Back to top button