Om du har programmerat i JavaScript ett tag har du säkert hört talas om... pilfunktioner och det berömda speciella beteendet hos dettaOch det är också ganska troligt att ditt huvud har exploderat mer än en gång i försöket att förstå varför. detta En sak gäller för en normal funktion och något helt annat för en pilfunktion.
Att behärska dessa två koncept är inte bara en fråga om teori: det är nyckeln till att skriva. Modern kod i JavaScript och ReactFörstå hur händelsehanterare fungerar, hur kontext beter sig inom klasser, hur asynkrona återanrop löses i Node.js och varför vissa mönster anses vara god praxis idag.
Först och främst: en snabb genomgång av funktioner i JavaScript
I JavaScript kan du deklarera funktioner på flera sätt, och varje sätt har konsekvenser för hur de beter sig. detta, I omfattning och för tillfället är funktionen tillgänglig i koden.
Funktionsdeklaration
En klassisk funktionsdeklaration använder nyckelordet fungera och ett namn. Dessa typer av funktioner är "förhöjda" (hissning) i början av omfånget där de deklareras, vilket gör att funktionen kan anropas redan innan dess definition i filen.
I en funktion som deklareras på ett traditionellt sätt, värdet av detta bestäms enligt hur Funktionen anropas: om den anropas som en metod för ett objekt, om den används med ny, om den är i strikt läge, om den växlar till Ring, tillämpa o bindaetc. Denna flexibilitet är kraftfull, men den är också källan till många fel i komplex kod.
Funktionsuttryck
Du kan tilldela en anonym eller namngiven roll till en variabelI så fall höjs inte själva funktionen, endast variabeldeklarationen höjs (med var), men inte dess värde. Med Låt y const Du kan inte ens använda den före dess definitionslinje på grund av temporal död zon.
Dessa funktioner delar samma modell av denna dynamik Till skillnad från klassiska deklarationer: deras värde beror alltid på anropskontexten, så de förblir känsliga för hur du skickar dem som en återanropsfunktion eller hur du använder dem inom klasser och objekt.
Vad exakt är pilfunktioner?
Pilfunktionerna introducerades med ECMAScript 2015 (ES6) som ett mer bekvämt, kompakt och uttrycksfullt sätt att deklarera funktioner, särskilt för återanrop och funktionell programmering med metoder som karta, filtrera o minska.
Dess grundläggande syntax består av en lista med parametrar, följt av operatorn => och sedan en uttryck eller ett kodblockDetta kompakta format gör koden mer effektiv. läsbar i många scenarier där man tidigare var tvungen att skriva långa och repetitiva anonyma funktioner.
Grundläggande syntax för pilfunktioner
Pilfunktioner stöder flera syntaxvariationer, alla baserade på samma regler men med genvägar för enklare fall. Att förstå varje form hjälper dig att skriva bättre. renare kod Jag kan snabbt se vad varje återuppringning gör.
- Utan parametrarTomma parenteser används före pilen och vanligtvis ett uttryck efter.
const getMessage = () => "Hola"; - En enda parameterDu kan utelämna parenteserna runt parameternamnet, vilket gör koden mer koncis.
const square = x => x * x; - Flera parametrarDu måste alltid använda parenteser som omsluter dem, separerade med kommatecken.
const sum = (a, b) => a + b; - Huvuddelen av ett enda uttryckOm du inte använder klammerparenteser returneras uttrycket på ett visst sätt. implicit utan att behöva skriva avkastning.
- Flerlinjers kroppOm du använder tandställning måste du skriva avkastning explicit för att returnera ett värde, precis som i en vanlig funktion.
En vanlig felkälla är att glömma bort avkastning När du går från en enradig version till en brödtext med klammerparenteser för att lägga till mer logik, börjar plötsligt din funktion återvända odefinierad Och det är inte uppenbart varför.
Pilfunktioner och literala objekt
Om en pilfunktion med ett enda uttryck returnerar en bokstavligt objektDu måste omsluta objektet inom parenteser så att motorn inte misstar det för början av funktionsblocket. Det är en liten syntaxfälla som överraskar många.
Annars tolkar tolken nycklarna som ett tomt block och din funktion returnerar inte objektet, men odefinieradDetta orsakar svårspårade fel när du försöker komma åt egenskaper som aldrig returnerades.
Pilfunktioner och deras beteende
Den verkligt differentierande faktorn för pilfunktioner är inte syntaxen, utan deras modell av dettaMedan traditionella funktioner har en denna dynamik Beroende på deras namn har pilfunktioner en detta lexikonDet vill säga, de fångar upp Detta är från det område där de skapades och de behåller det oförändrat.
Detta innebär att inom en pilfunktion, detta Det kan inte ändras med Ring, tillämpa ni bindaOch det beror inte på om du använder det som ett callback i en händelsehanterare eller skickar det till en annan funktion. Det kommer alltid att ha samma värde som det hade utanför den vid tidpunkten för dess definition.
Hur fungerar detta i vanliga funktioner?
I en funktion deklarerad med fungera, värdet av detta Det beror på sammanhanget:
- Om du anropar funktionen som en metod för ett objekt, detta Peka på det objektet.
- Om du anropar den utan kontext i icke-strikt läge, detta hänvisning till globalt objekt (fönster i webbläsaren, globalt i Node.js).
- Om du använder ny, detta den pekar sedan på nyskapad instans.
- Om du ansöker Ring, tillämpa o binda, kan du tvinga fram värdet på detta manuellt till det objekt du väljer.
I strikt läge, om du anropar en normal funktion utan ett objekt som mottagare och utan att använda Ring eller liknande, detta kommer att vara odefinieradDet är därför man i många moderna exempel ser funktioner som börjar med 'use strict' för att undvika tysta fel och tvinga fram ett mer konsekvent beteende.
Hur fungerar detta i pilfunktioner?
När det gäller pilfunktioner, detta Den beräknas inte om när den anropas; den tas direkt från område där de definieradesDet vill säga, de beter sig på liknande sätt som hur variabler fångas upp genom stängning (förslutning): De blickar utåt och kommer ihåg det värdet för alltid.
Detta har flera mycket viktiga konsekvenser för ditt dagliga liv:
- Du kan inte använda en pilfunktion som konstruktör med nyeftersom de inte har sina egna detta inte heller den lämpliga prototypen.
- Anropa en pilfunktion med Ring o tillämpa kommer inte att ändra värdet på dettaDu kommer dock att kunna skicka vidare resten av argumenten normalt.
- I metoder för objekt som skapats som pilfunktioner, detta Den kommer inte att peka mot objektet, utan mot övre miljö (till exempel fönstret eller modulen), vilket nästan alltid är en bugg.
Den viktigaste slutsatsen är att pilfunktioner är idealiska när du vill ha Denna interna är densamma som den externa.Men de är en dålig idé när du behöver en verklig objektmetod eller när du avser att instansiera något med ny.
Pilfunktioner, klasser och React

I världen av Reagera och av JavaScript-klasser i allmänhet, hanteringen av detta Det är en känslig fråga. Klasskomponenterna som ärvts från React.Component De använder metoder där detta måste konsekvent peka på komponentinstansen för att få åtkomst detta.rekvisita y detta.tillstånd.
Med metoder definierade med klassisk klasssyntax, värdet av detta Den kan "förloras" när en metod skickas som ett händelseanrop, till exempel till onclickDet är därför du i många äldre handledningar ser mönster som this.metod.bind(detta) i konstruktorn eller direkt i JSX, något som anses vara ganska utdraget nuförtiden.
Typiska bindningsmönster i React
I en klasskomponent fanns det historiskt sett fyra huvudsakliga sätt att säkerställa att detta pekar alltid på komponenten när den anropas från en händelsehanterare:
- Tillämpa binda i metoden ger, vilket säkerställer kontext men skapar en ny funktion i varje rendering.
- Gör bind i konstruktorn för att undvika att återskapa funktionen om och om igen, genom att bibehålla en enda referens.
- Definiera hanteraren som klassegenskap med pilfunktion, och utnyttjar det faktum att pilfunktionerna fångar detta av instansen.
- Använd pilfunktion inline i JSX, skickar direkt en pilfunktion till attributet onclick eller motsvarande.
I praktiken är möjligheten att deklarera hanteraren som klassegenskap med hjälp av en pilfunktion Det blev det bekvämaste mönstret: du skriver mindre kod, du slipper göra binda manuell och detta sikta alltid på komponenten utan överraskningar.
Pilfunktioner i funktionskomponenter
Med ankomsten av funktionella komponenter och krokarReact förlitar sig mycket mer på pilfunktioner. De flesta moderna komponenter implementeras som pilar. const MinKomponent = () => { … }Detta gör användningen av pilfunktioner naturlig och konsekvent i hela trädet.
Inom dessa komponenter är det vanligt att kombinera pilfunktioner med andra ES6-funktioner som t.ex. destrukturering, mall bokstäver, ternära operatorer, spridning och arraymetoder som karta, filtrera y minska, vilket konstruerar en mycket uttrycksfull syntax men en som kräver god hantering av kontext och oföränderlighet.
Detta i ett globalt sammanhang, individuella objekt och funktioner
För att förstå varför pilfunktioner förändrar spelet så mycket är det värt att granska hur de beter sig. detta i olika native JavaScript-kontexter, både i webbläsaren och i Node.js.
I webbläsarens globala omfattning, utanför någon funktion eller modul, detta peka på objektet fönsterDet betyder att vilken variabel som helst som deklareras med var På en högre nivå hänger den på det globala objektet och är tillgänglig både med namn och med fönsternamnDetta anses vara dålig praxis i komplexa tillämpningar på grund av global förorening.
detta inom bokstavliga objekt
När du definierar en bokstavligt objekt Med traditionella metoder får varje metod en detta som refererar till själva objektet när det anropas som obj.metod()Detta är standardsättet för att komma åt systerfastigheter med den här.egenskapen utan behov av mellanliggande variabler.
Problemet uppstår när man ersätter dessa metoder med pilfunktionerEftersom vi inte har denna egenFunktionen arrow kommer att fånga den externa `this`, vilket troligen är den globala eller modulen `this`. Som ett resultat kommer du inte att kunna komma åt objektets egenskaper inom metoden med hjälp av detta, vilket helt bryter mot designens ursprungliga avsikt.
Detta är inom lösa funktioner och strikt läge
I traditionella funktioner som kallas "unplugged", utan ett mottagande objekt, är värdet av detta Det beror på om koden finns i strikt läge Eller inte. Strängt taget faller det tillbaka i globalt objekt, medan det strikt taget blir uttryckligen odefinieradför att undvika farliga handlingar på den globala miljön.
Detta beteende förändras drastiskt när du inkapslar din kod i ES-moduler, moderna bundlers eller IIFE:er, där den globala koden kan vara annorlunda eller inte bunden till detta på vanligt sätt, särskilt i node.js, där arkivets huvudsakliga omfattning är att modul och inte den globala.
anropa, tillämpa, binda och deras förhållande till detta
JavaScript erbjuder tre allmänt använda metoder jämfört med klassiska funktioner för att hantera kontext: Ring, tillämpa y bindaDe låter dig alla explicit kontrollera vilket värde det tar. detta när funktionen exekveras.
Dessa metoder fungerar bara med funktioner deklarerade med funktion (eller motsvarande), eftersom pilfunktioner ignorerar alla försök att ändra deras `detta`. Det är därför alla tre är så användbara när man arbetar med äldre API:er, gamla klasser eller mönster där man vill återanvända samma funktion på olika objekt.
anropa och tillämpa: anropa med explicit detta
så Ring som tillämpa de utför funktionen omedelbartDen enda skillnaden är hur du skickar argumenten. I båda fallen är den första parametern du skickar det objekt som ska konverteras. detta inom funktionen.
med Ring De efterföljande argumenten anges ett efter ett, medan med tillämpa De utger sig för att vara en arrayDetta passar mycket bra i situationer där du redan har argumentlistan som en samling och du vill injicera den som den är i anropet.
bind: skapa en ny funktion med `this`-uppsättningen
Metoden binda Den utför inte funktionen just nu, utan genererar istället en ny funktion med detta permanent förknippade till det objekt du anger. Det är som att skapa en "specialiserad" version av originalet som alltid kommer att bete sig inom det sammanhanget.
Det här mönstret är mycket vanligt för att skicka objektmetoder till API:er som senare anropar dem, till exempel som händelseåteranrop, utan att förlora den ursprungliga kontexten. Innan populariseringen av pilfunktioner, binda Det var ett av de renaste sätten att säkerställa att en klassmetod fortsatte att peka på rätt instans.
Varför är pilfunktioner idealiska för återanrop?
En av anledningarna till att pilfunktioner har erövrat JavaScript-ekosystemet är deras lämplighet för asynkrona återanrop och funktionell programmering. När man arbetar med API:er som setTimeout, setInterval, lovar Eller med Node.js-händelseslingan behöver du ofta komma åt den externa kontexten utan att den förvrängs.
Före ES6 användes knep som att spara konstant själv = detta o konstant att = detta innan återanropet anges, så att rätt objekt fortfarande kan refereras inifrån funktionen. Pilfunktioner gör detta onödigt genom att automatiskt fånga in Denna externa och bevara den.
Pilfunktioner och händelseslingan i Node.js
En node.js, där det mesta av den intressanta koden kretsar kring Asynkron I/O (filer, nätverk, timers), kombinationen av pilfunktioner med modellen av händelseslinga och återuppringningsköer Det förenklar livet mycket. Varje gång du skickar en funktion som en hanterare till fs.läsFilOavsett om du skickar en HTTP-server eller någon libuv-operation, hjälper pilfunktioner till att hålla referenser till sammanhängande variabler och modulkontexter fria från överraskningar.
Dessutom, i mikrouppgiftsköer som de hos lovar o process.nextTickDet är mycket vanligt att skriva korta återuppringningar som sedan o fångst med pilsyntax, och utnyttjar båda implicit avkastning som detta lexikon, vilket gör koden mer deklarativ och uttrycksfull.
När ska man INTE använda pilfunktioner?
Även om pilfunktioner kan verka som den magiska lösningen på alla problem, finns det scenarier där deras användning är direkt kontraproduktivt och genererar till och med buggar som är svåra att upptäcka.
En av dem, kanske den viktigaste, är metoder för objekt eller klasser som behöver sin egen `detta`En annan är när du vill skapa byggare eller använda objektorienterade mönster som stöds av prototyper och arv, där avsaknaden av Prototypen och dess eget sammanhang gör att pilfunktioner helt enkelt inte passar.
Undvik att använda pilfunktioner som objektmetoder.
Om du definierar en metod för ett objekt med en pilfunktion, Denna interna kommer inte att referera till objektetutan snarare till det sammanhang i vilket objektet definierades. Detta bryter mot förväntningarna hos alla som läser koden och avser att använda den här.egenskapen inom metoden.
Det rekommenderade sättet att skriva metoder som är beroende av detta Det handlar om att fortsätta använda den klassiska syntaxen i literala objekt eller att deklarera dessa metoder på ett standardiserat sätt i klasser, och reservera pilfunktioner för interna återanrop som behöver fånga detta från en överlägsen metod.
Använd inte pilfunktioner som konstruktorer
Pilfunktionerna saknas [[Konstruera]], den interna mekanismen som gör att en funktion kan anropas med nyDärför kommer ett försök att använda dem som konstruktorer att generera en SkrivfelDe har inte heller några egna. PrototypenDärför kan du inte låsa delade metoder för instanser.
Om din design kräver återanvändbara instanser med gemensamma egenskaper och metoder bör du välja klasser eller genom traditionella konstruktorfunktioner, vilket lämnar pilfunktioner för tillståndslös logik eller rent funktionella verktyg.
Samband med andra moderna funktioner i JavaScript
Pilfunktioner används sällan isolerat; de går nästan alltid hand i hand med andra funktioner i Modern ECMAScript vilket också påverkar hur du organiserar och förstår kod. Några av de mest relaterade är Låt y consti oföränderlighet, The destrukturering och spridningssyntax.
Att veta hur man kombinerar dem korrekt låter dig skriva mycket koncisa pilfunktioner som transformerar data med arraymetoder som karta, filtrera y minskaatt alltid behålla det ursprungliga tillståndet intakt, något avgörande i miljöer som React där oföränderlighetsmönstret är normen.
låt, konstant och oföränderlighet
Ankomsten av Låt y const Det möjliggjorde arbete med blockomfång och med referenser som inte omtilldelas. I kombination med pilfunktioner är det idag vanligt att deklarera funktioner som const minFunktion = () => {…}, vilket säkerställer att variabeln som innehåller funktionen inte skrivs över.
I datastrukturer som arrayer och objekt uppmuntras oföränderlighet genom användning av Objektfrysning när du vill undvika djupa förändringar, och genom spridningsoperationer och rena metoder när du behöver skapa modifierade kopior istället för att ändra originalet, är något särskilt viktigt vid rendering eller tillstånd beroende av att detektera förändringar.
Mallliteraler, ternärer och kortslutning
Vid rendering av återanrop, till exempel i JSX eller vid konstruktion av HTML-strängar, är det nästan oundvikligt att blanda pilfunktioner med mall bokstäver (backticks) och operatörer som ternär o kortslutningsutvärderingDessa operatorer låter dig skriva mycket uttrycksfulla inline-villkor som passar mycket bra med den deklarativa stilen hos pilfunktioner.
Inom mallliteraler, uttrycken ${…} De upplöses med samma lexikala omfång som dominerar "detta" för pilfunktioner, så det är viktigt att vara tydlig med vilka variabler och vilken kontext som används i varje interpolering för att undvika tvetydigt beteende.
Pilfunktioner i iterationer och samlingar
Ett av de områden där pilfunktioner har gjort störst skillnad är i bearbetningen av arrayer och samlingarSkicka korta återanrop till metoder som karta, filtrera, finna o minska Det blir betydligt mer läsbart med pilsyntaxen.
Till exempel blir filtrering av poster i en lista baserat på en kategori eller omvandling av API-resultat till interna datastrukturer mycket mer deklarativt, och i kombination med destrukturering Det är enkelt att bara extrahera de egenskaper du behöver inifrån funktionens parametrar.
kartlägg, filtrera, reducera och företag
Den typiska kombinationen brukar vara något i stil med denna: list.filter(item => condition).map(item => transformation)kedjar flera pilfunktioner till arraymetoder. Var och en av dem ärver `this` från det sammanhang där det definieras, även om `this` i de flesta av dessa fall inte ens används, utan snarare explicita parametrar, vilket förstärker den funktionella stilen.
En minskaI det här fallet, där du hanterar en ackumulator som går från iteration till iteration, låter pilfunktioner dig uttrycka reduktionsoperationen mycket koncist, även om det är lämpligt att inte överanvända alltför täta uttryck om du vill att koden ska förbli förståelig för andra utvecklare.
Import, export och moduler
I det moderna JavaScript-ekosystemet är nästan all kod organiserad i modulerantingen med hjälp av inbyggd syntax import / export eller genom att använda paketeringssystem som kompilerar till det formatet. I detta sammanhang passar pilfunktioner mycket bra som återanvändbara funktionalitetsenheter.
Det är vanligt att se verktyg exporterade som const myUtil = (…) => {…} och sedan importeras till olika filer. Eftersom varje modul finns i sitt eget omfång, detta lexikon Kontexten för pilfunktioner tenderar att vara den för modulen, vilket i de flesta rena verktyg inte ens är relevant, eftersom de inte behöver kontext.
Bästa praxis med externa moduler och skript
När man serverar JavaScript på en HTML-sida är det att föredra att ladda det med hjälp av externa filer och undvik att bädda in stora inline-block. Använd även attributet typ="modul" i script-taggen aktiverar modern syntax import / export och gör det tydligt att filen körs i läge sträng standard.
Angående prestanda, att placera modulskripten i huvud med uppskjuta låter webbläsaren ladda ner och köra koden utan att blockera rendering av HTML, samtidigt som en bra användarupplevelse bibehålls. I hela detta schema är pilfunktioner helt enkelt det mest naturliga sättet att definiera små delar av återanvändbar logik.
Infoga och manipulera DOM med modern JavaScript
Även om fokus i detta ämne ligger på pilen och `detta`-funktionerna, är det omöjligt att ignorera JavaScripts roll i DOM-manipulationMetoder som getElementById, querySelector, setAttribute eller modifieringen av stil De är fortfarande grundläggande, men idag kombineras de vanligtvis med pilanrop för att reagera på händelser och uppdatera gränssnittet.
Arbeta med evenemang lyssnare med element.addEventListener('click', e => {...}) Detta är standardsättet att koppla logik till användarinteraktioner. I dessa hanterare, detta Inuti arrow-funktionen kommer det inte att vara DOM-elementet, utan den externa kontexten, så om du behöver noden måste du använda själva händelseparametern. e.nuvarandeMål o e.mål, istället för att förlita sig på detta som med de gamla inline-attributen.
Synkron JavaScript, asynkron JavaScript och återanrop
Ett annat viktigt område där pilfunktioner gör skillnad är inom asynkron programmeringJavaScript är enkeltrådat och, för att undvika att blockera gränssnittet, delegerar det långsamma operationer till webbläsar- eller nod-API:er som sedan anropar dina återanrop när de är klara.
Skicka pilfunktioner som återanrop till setTimeout, löftenHTTP-förfrågningar eller diskåtkomster gör koden mer kompakt och bibehåller kontext utan ytterligare knep. I steget med kapslade återanrop mot lovar och senare mot asynkronisera / väntaPilfunktioner har varit en viktig del av att göra dataflödet läsbart.
Sammantaget, att förstå hur pilfunktioner fångar detta lexisktAtt veta när de är din bästa allierade och när det är bäst att fortsätta förlita sig på traditionella funktioner, tillsammans med att veta hur man kombinerar dem med moduler, arraymetoder, oföränderlighet, klasser och händelseslingan, är det som gör att du kan skriva verkligt stabila JavaScript och React. I slutändan handlar det om att välja rätt funktionsform för varje kontext, dra nytta av dess syntaktiska fördelar utan att hamna i gränsfallen där dess "detta" beteende kan slå tillbaka. Dela den här programmeringsguiden och JavaScript-pilfunktionerna.