7 tips att hantera odefinierad i JavaScript

De flesta moderna språk som Ruby, Python eller Java har ett enda nullvärde (nil eller null), vilket verkar vara ett rimligt tillvägagångssätt.

Men JavaScript är annorlunda.

null, men också undefined, representerar tomma värden i JavaScript. Så vad är den exakta skillnaden mellan dem?

Det korta svaret är att JavaScript-tolk returnerar undefined vid åtkomst till en variabel eller objektegenskap som ännu inte har initierats. Till exempel:

På andra sidan representerar null ett saknat objekt referens. JavaScript initierar inte variabler eller objektegenskaper med null.

Vissa inbyggda metoder som String.prototype.match() kan returnera null för att beteckna ett saknat objekt. Ta en titt på exemplet:

Eftersom JavaScript är tillåtet har utvecklare frestelsen att komma åt oinitialiserade värden. Jag är också skyldig till så dålig praxis.

Ofta genererar sådana riskabla åtgärder undefined relaterade fel:

  • TypeError: "undefined" is not a function
  • TypeError: Cannot read property "<prop-name>" of undefined
  • och liknande typfel.

JavaScript-utvecklare kan förstå ironin i detta skämt :

För att minska sådana fel måste du förstå de fall då undefined genereras. Låt oss utforska undefined och dess inverkan på kodsäkerhet.

1. Vad är odefinierad

JavaScript har 6 primitiva typer:

Och en separat objekttyp: {name: "Dmitri"} , .

Från 6 primitiva typer är undefined ett specialvärde med sin egen typ Odefinierad. Enligt ECMAScript-specifikationen:

Odefinierat värde primitivt värde används när en variabel inte har tilldelats ett värde.

Standarden definierar tydligt att du kommer att få undefined när du öppnar oinitialiserade variabler, icke-existerande objektegenskaper, icke-existerande arrayelement och lika.

Några exempel:

Ovanstående exempel visar att åtkomst till:

  • en oinitialiserad variabel number
  • en icke-existerande objektegenskap movie.year
  • eller ett icke-existerande arrayelement movies

utvärderas till undefined.

ECMAScript-specifikationen definierar typen av undefined -värde:

Odefinierad typ är en typ vars enda värde är undefined -värdet.

I denna mening typeof -operatören returnerar "undefined" -sträng för ett undefined -värde:

Självklart fungerar typeof för att verifiera om en variabel innehåller ett undefined -värde:

2. Scenarier som skapar odefinierad

2.1 Oinitialiserad variabel

En deklarerad variabel men ännu inte tilldelad ett värde (oinitialiserad) är som standard undefined.

Enkelt och enkelt:

myVariable deklareras och har ännu inte tilldelats något värde. Åtkomst till variabeln utvärderas till undefined.

Ett effektivt tillvägagångssätt för att lösa problem med oinitialiserade variabler är när det är möjligt tilldela ett initialt värde. Ju mindre variabeln finns i oinitialiserat tillstånd, desto bättre.

Helst skulle du tilldela ett värde direkt efter deklarationen const myVariable = "Initial value". Men det är inte alltid möjligt.

Tips 1: Favorit const, annars använd let, men säg adjö till var

Enligt min mening är en av de bästa funktionerna i ECMAScript 2015 det nya sättet att deklarera variabler med const och let. Det är ett stort steg framåt.

const och let är blockomfattade (i motsats till äldre funktion scoped var) och finns i en tidsmässig dödzon tills deklarationsraden.

Jag rekommenderar const variabel när dess värde inte kommer att ändras. Det skapar en oföränderlig bindning.

En av de fina funktionerna i const är att du måste tilldela variabeln const myVariable = "initial" ett initialvärde. Variabeln exponeras inte för det oinitialiserade tillståndet och åtkomst till undefined är omöjligt.

Låt oss kontrollera funktionen som verifierar om ett ord är en palindrom:

length och half variabler tilldelas ett värde en gång. Det verkar rimligt att förklara dem som const eftersom dessa variabler inte kommer att förändras.

Använd let deklaration för variabler vars värde kan ändras. När det är möjligt tilldela ett initialvärde direkt, t.ex. let index = 0.

Hur är det med den gamla skolan var? Mitt förslag är att sluta använda det.

var deklarationsproblem är variabellyftning inom funktionsomfånget. Du kan förklara en var variabel någonstans i slutet av funktionsomfånget, men ändå kan du komma åt den före deklarationen: och du får en undefined.

myVariable är tillgänglig och innehåller undefined redan före deklarationsraden: var myVariable = "Initial value".

Tvärtemot kan en const eller let -variabel inte nås innan deklarationsraden – variabeln befinner sig i en tidsmässig dödzon före förklaringen. Och det är trevligt eftersom du har mindre chans att få tillgång till en undefined.

Exemplet ovan uppdaterat med let (istället av var) kastar en ReferenceError eftersom variabeln i den temporala döda zonen inte är tillgänglig.

Att uppmuntra användningen av const för oföränderliga bindningar eller let garanterar annars en övning som minskar utseendet på de icke-initierade variabel.

Tips 2: Öka sammanhållningen

Sammanhållning karaktäriserar i vilken grad elementen i en modul (namnområde, klass, metod, kodblock) hör samman. Sammanhållningen kan vara hög eller låg.

En modul med hög sammanhållning är att föredra eftersom elementen i en sådan modul fokuserar enbart på en enda uppgift. Det gör modulen:

  • Fokuserad och förståelig: lättare att förstå vad modulen gör
  • Underhållbar och lättare att refaktorera: förändringen i modulen påverkar färre moduler
  • Återanvändbar: att vara fokuserad på en enda uppgift, det gör det lättare att återanvända modulen
  • Testbar: du skulle lättare testa en modul som är inriktad på en enda uppgift

Hög sammanhållning åtföljd av lös koppling är kännetecknet för ett väldesignat system.

Ett kodblock kan betraktas som en liten modul. För att dra nytta av fördelarna med hög sammanhållning, håll variablerna så nära kodblocket som använder dem.

Till exempel, om en variabel endast existerar för att bilda logiken för blockomfånget, ska du sedan deklarera och göra variabeln levande endast inom det blocket (med const eller let förklaringar). Exponera inte denna variabel för det yttre blockomfånget, eftersom det yttre blocket inte borde bry sig om denna variabel.

Ett klassiskt exempel på variabler som är onödigt förlängda är användningen av for cykla inuti en funktion:

index, item och length variabler deklareras i början av funktionskroppen. De används dock bara nära slutet. Vad är problemet med detta tillvägagångssätt?

Mellan deklarationen högst upp och användningen i for uttalande variablerna index, item är oinitialiserade och exponerade för undefined. De har en orimligt lång livscykel i hela funktionsomfånget.

Ett bättre tillvägagångssätt är att flytta dessa variabler så nära deras användningsplats som möjligt:

index och item variabler finns bara i blockomfånget för for uttalande. De har ingen betydelse utanför for.
length variabel förklaras också nära källan till dess användning.

Varför är den modifierade versionen bättre än den ursprungliga? Låt oss se:

  • Variablerna exponeras inte för oinitialiserat tillstånd, så du har ingen risk att komma åt undefined
  • Flytta variabler så nära deras användningsplats som möjligt ökar kodläsbarheten
  • Höga sammanhängande bitar av kod är lättare att omforma och extrahera till separata funktioner, om det behövs

2.2 Åtkomst en icke-existerande egenskap

När du öppnar en icke-existerande objektegenskap returnerar JavaScript undefined.

Låt oss visa det i ett exempel:

favoriteMovie är ett objekt med en enda egenskap title. Åtkomst till en ej befintlig egendom actors med hjälp av en fastighetsaccessor favoriteMovie.actors utvärderas till undefined.

Åtkomst till en icke-befintlig egendom ger inget fel. Problemet uppstår när man försöker få data från den icke-existerande egenskapen, som är den vanligaste undefined -fällan, vilket återspeglas i det välkända felmeddelandet TypeError: Cannot read property <prop> of undefined.

Låt oss modifiera det tidigare kodavsnittet något för att illustrera ett TypeError kast:

favoriteMovie har inte egenskapen actors, så favoriteMovie.actors utvärderas till undefined.

Som ett resultat, åtkomst till det första objektet i ett undefined -värde med uttrycket favoriteMovie.actors kastar ett TypeError.

Den tillåtna karaktären av JavaScript som tillåter åtkomst till icke-existerande egenskaper är en källa till icke-bestämning: egenskapen kan ställas in eller inte. Det bra sättet att kringgå detta problem är att begränsa objektet så att det alltid har definierat de egenskaper som det innehåller.

Tyvärr har du ofta inte kontroll över objekten. Sådana objekt kan ha en annan uppsättning egenskaper i olika scenarier. Så du måste hantera alla dessa scenarier manuellt.

Låt oss implementera en funktion append(array, toAppend) som lägger till i början och / eller i slutet av en rad nya element. toAppend -parametern accepterar ett objekt med egenskaper:

  • first: element infogat i början av array
  • last: element infogat i slutet av array.

Funktionen returnerar en ny matrisinstans utan att ändra den ursprungliga matrisen.

Den första versionen av append(), lite naiv, kan se ut så här:

Eftersom toAppend objekt kan utelämna first eller last egenskaper, det är obligatoriskt att verifiera om dessa egenskaper finns i toAppend.

En fastighetsaccessor utvärderas till undefined om egenskapen inte finns. Den första frestelsen att kontrollera om first eller last egenskaper är att verifiera dem mot undefined. Detta utförs i villkor if(toAppend.first){} och if(toAppend.last){}

Inte så snabbt. Detta tillvägagångssätt har en nackdel. undefined samt false, null, 0, NaN och "" är falska värden.

I den nuvarande implementeringen av append() tillåter funktionen inte att infoga falska element:

Tipsen som följer förklarar hur man korrekt kontrollerar fastighetens existens.

Tips 3: Kontrollera egendomens existens

Lyckligtvis erbjuder JavaScript en massa sätt att avgöra om objektet har en specifik egenskap:

  • obj.prop !== undefined: jämför med undefined direkt
  • typeof obj.prop !== "undefined": verifiera egenskapens värdetyp
  • obj.hasOwnProperty("prop"): verifiera om objekt har en egen egendom
  • "prop" in obj: verifiera om objektet har en egen eller ärvt egendom

Min rekommendation är att använd in operatör. Den har en kort och söt syntax. in operatörsnärvaro föreslår en tydlig avsikt att kontrollera om ett objekt har en specifik egenskap utan att få tillgång till det verkliga egenskapsvärdet.

obj.hasOwnProperty("prop") är också en bra lösning. Det är något längre än in -operatören och verifierar endast i objektets egna egenskaper.

Låt oss förbättra append(array, toAppend) -funktionen med in operatör:

"first" in toAppend (och "last" in toAppend) är true om motsvarande egenskap finns, false annars.

in operatören löser problemet med att infoga falska element 0 och false. Att lägga till dessa element i början och slutet av ger nu det förväntade resultatet .

Tips 4: Förstöring för att komma åt objektegenskaper

När du öppnar en objektegenskap är det ibland nödvändigt att ange ett standardvärde om egenskapen inte finns.

Du kan använda in tillsammans med ternär operatör för att åstadkomma det:

Syntax för ternär operatör blir skrämmande när antalet egenskaper att kontrollera ökar. För varje fastighet måste du skapa en ny kodrad för att hantera standardvärdena, vilket ökar en ful vägg av liknande utseende ternära operatörer.

För att använda ett mer elegant tillvägagångssätt, låt oss bekanta oss med en fantastisk ES2015-funktion som kallas objektförstöring.

Objektdestrukturering möjliggör integrerad utvinning av objektegenskapsvärden direkt i variabler och anger ett standardvärde om egenskapen inte finns. En bekväm syntax för att undvika att hantera direkt med undefined.

Egenskapsextraktionen är nu exakt:

För att se saker i aktion, låt oss definiera en användbar funktion som slår in en sträng i citat.

quote(subject, config) accepterar det första argumentet som strängen som ska slås in. Det andra argumentet config är ett objekt med egenskaperna:

Tillämpar fördelarna med att objektet förstörs, låt oss implementera quote():

const { char = """, skipIfQuoted = true } = config destruktionstilldelning på en rad extraherar egenskaperna char och skipIfQuoted från config -objekt.
Om vissa egenskaper saknas i config -objektet, anger destruktureringsuppgiften standardvärdena : """ för char och false för skipIfQuoted.

Lyckligtvis har funktionen fortfarande utrymme för förbättringar.

Låt oss flytta destruktionstilldelningen till parametrar. Och ställ in ett standardvärde (ett tomt objekt { }) för parametern config för att hoppa över det andra argumentet när standardinställningarna räcker.

Destruktureringsuppdraget ersätter parametern config i funktionens signatur. Jag gillar det: quote() blir en rad kortare.

= {} till höger om destruktureringsuppdraget säkerställer att ett tomt objekt används om det andra argumentet inte alls anges quote("Sunny day").

Objektdestruktion är en kraftfull funktion som effektivt hanterar utvinning av egenskaper från objekt. Jag gillar möjligheten att ange ett standardvärde som ska returneras när egenskapen som nås inte finns. Som ett resultat undviker du undefined och besväret kring det.

Tips 5: Fyll objektet med standardegenskaper

Om det inte finns något behov av att skapa variabler för varje egendom, som destruktionstilldelningen gör, kan objektet som saknar vissa egenskaper fyllas med standardvärden.

ES2015 Object.assign(target, source1, source2, ...) kopierar värdena för alla uppräknade egna egenskaper från ett eller flera källobjekt till målobjektet. Funktionen returnerar målobjektet.

Du måste till exempel komma åt egenskaperna för unsafeOptions -objekt som inte alltid innehåller dess fullständiga uppsättning egenskaper.

För att undvika undefined vid åtkomst till en icke-befintlig egendom från unsafeOptions, låt oss göra några justeringar:

  • Definiera ett objekt defaults som innehåller standardvärden för egendom
  • Ring Object.assign({ }, defaults, unsafeOptions) för att bygga ett nytt objekt options. Det nya objektet tar emot alla egenskaper från unsafeOptions, men de saknade hämtas från defaults.

unsafeOptions innehåller endast fontSize -egenskap. defaults objekt definierar standardvärdena för egenskaper fontSize och color.

Object.assign() tar det första argumentet som ett målobjekt {}. Målobjektet får värdet av fontSize -egenskapen från unsafeOptions källobjekt. Och värdet av color egenskap från defaults källobjekt, eftersom unsafeOptions inte innehåller color.

Den ordning i vilken källobjekten räknas upp spelar roll: senare källobjektegenskaper skriver över tidigare.

Du är nu säker på att få tillgång till alla egenskaper för options -objekt, inklusive options.color som inte var tillgängligt i unsafeOptions från början.

Lyckligtvis finns det ett enklare alternativ att fylla objektet med standardegenskaper.Jag rekommenderar att du använder spridningsegenskaperna i objektinitierare.

I stället för Object.assign() anrop, använd objektspridningssyntaxen för att kopiera till målobjektet alla egna och uppräkbara egenskaper från källobjekt:

objektinitialiserare sprider egenskaper från defaults och unsafeOptions källobjekt. Ordningen i vilken källobjekten anges är viktig: egenskaper för senare källobjekt skriver över tidigare.

Att fylla ett ofullständigt objekt med standardegenskapsvärden är en effektiv strategi för att göra din kod säker och hållbar. Oavsett situationen innehåller objektet alltid hela uppsättningen egenskaper: och undefined kan inte genereras.

Bonustips: nullish coalescing

Operatorn nullish coalescing utvärderas till ett standardvärde när dess operand är undefined eller null:

Nullish coalescing operator är bekvämt att komma åt en objektegenskap samtidigt som det har ett standardvärde när den här egenskapen är undefined eller null:

styles objektet har inte egenskapen color, alltså styles.color egendomstillbehör är undefined. styles.color ?? "black" utvärderas till standardvärdet "black".

styles.fontSize är 18, så att den ogiltiga sammanslagningsoperatören utvärderar till egenskapens värde 18.

2.3 Funktionsparametrar

Funktionsparametrarna är implicit standard för undefined.

Vanligtvis bör en funktion som definieras med ett visst antal parametrar åberopas med samma antal argument. Det är då parametrarna får de värden du förväntar dig:

När multiply(5, 3), parametrarna a och b får 5 respektive 3 värden. Multiplikationen beräknas som förväntat: 5 * 3 = 15.

Vad händer när du utelämnar ett argument om anrop? Motsvarande parameter inuti funktionen blir undefined.

Låt oss modifiera det tidigare exemplet något genom att anropa funktionen med bara ett argument:

Inropet multiply(5) utförs med ett enda argument: som resultat a är 5 b -parametern är undefined.

Tips 6: Använd standardparametervärde

Ibland kräver en funktion inte hela uppsättningen argument för anrop. Du kan ställa in standardvärden för parametrar som inte har något värde.

Med tanke på föregående exempel, låt oss göra en förbättring. Om b -parametern är undefined, låt den vara 2:

Funktionen anropas med ett enda argument multiply(5). Ursprungligen är a parametern 2 och b är undefined.
Det villkorliga uttalandet verifierar om b är undefined. Om det händer sätter b = 2 -tilldelning ett standardvärde.

Även om det angivna sättet att tilldela standardvärden fungerar rekommenderar jag inte att man jämför direkt med undefined. Det är ordentligt och ser ut som ett hack.

Ett bättre tillvägagångssätt är att använda ES2015-standardparametrarfunktionen. Det är kort, uttrycksfullt och inga direkta jämförelser med undefined.

Att lägga till ett standardvärde i parametern b = 2 ser bättre ut:

b = 2 i funktionssignaturen ser till att om b är undefined, parametern är som standard 2.

ES2015: s standardparametrar är intuitiv och uttrycksfull. Använd den alltid för att ställa in standardvärden för valfria parametrar.

2.4 Funktionsreturvärde

Implicit, utan return, en JavaScript-funktion returnerar undefined.

En funktion som inte har return uttalande returnerar implicit undefined:

returnerar inga beräkningsresultat. Funktionsanropsresultatet är undefined.

Samma situation händer när return uttalande är närvarande, men utan ett uttryck i närheten:

return; uttalande körs, men det returnerar inget uttryck. Anropsresultatet är också undefined.

Att indikera nära return fungerar naturligtvis som förväntat:

Nu utvärderas funktionsanropet till 4, vilket är 2 i kvadrat.

Tips 7: Lita inte på den automatiska insättningen av semikolon

Följande lista med uttalanden i JavaScript måste sluta med semikolon (;) :

  • tomt uttalande
  • let, const, var, import, export förklaringar
  • uttrycksuttalande
  • debugger uttalande
  • continue uttalande, break uttalande
  • throw uttalande
  • return uttalande

Om du använder en av ovanstående påståenden, var noga med att ange ett semikolon i slutet:

I slutet av båda let deklaration och return uttalande en obligatorisk semikolon skrivs.

Vad händer när du inte vill ange dessa semikolon? I en sådan situation tillhandahåller ECMAScript en automatisk semikoloninsättning (ASI) -mekanism, som infogar åt dig de saknade semikolonerna.

Hjälpt av ASI kan du ta bort semikolon från föregående exempel:

Ovanstående text är en giltig JavaScript-kod. De saknade semikolonna infogas automatiskt åt dig.

Vid första anblicken ser det ganska lovande ut. Med ASI-mekanismen kan du hoppa över onödiga semikolon. Du kan göra JavaScript-koden mindre och lättare att läsa.

Det finns en liten men irriterande fälla skapad av ASI. När en ny rad står mellan return och det returnerade uttrycket return \n expression infogar ASI automatiskt ett semikolon före den nya raden return; \n expression.

Vad betyder det i en funktion att ha return; uttalande? Funktionen returnerar undefined. Om du inte känner till ASI-mekanismen i detalj är den oväntat returnerade undefined vilseledande.

Låt oss till exempel studera det returnerade värdet av getPrimeNumbers() anrop:

Mellan return uttalande och matrisens bokstavliga uttryck finns en ny rad. JavaScript infogar automatiskt ett semikolon efter return och tolkar koden enligt följande:

Uttrycket return; gör funktionen getPrimeNumbers() för att returnera undefined istället för den förväntade matrisen.

Problemet löses genom att ta bort den nya raden mellan return och bokstavlig array:

Min rekommendation är att studera hur exakt automatisk insättning av semikolon fungerar för att undvika sådana situationer.

Lägg naturligtvis aldrig en ny rad mellan return och det returnerade uttrycket.

2,5 tomrumsoperatör

void <expression> utvärderar uttrycket och returnerar undefined oavsett resultatet utvärderingen.

Ett användningsfall för void -operatören är att undertrycka uttrycksutvärdering till undefined och förlitar sig på någon bieffekt av utvärderingen.

3. odefinierad i matriser

Du får undefined när du öppnar ett arrayelement med ett index utanför gränserna.

colors array har 3 element, så giltiga index är 0, 1 och 2.

Eftersom det inte finns några arrayelement vid index 5 och -1, kommer accessorerna colors och colors är undefined.

I JavaScript kan du stöta på så kallade glesa matriser. Avhandlingar är matriser som har luckor, dvs vid vissa index definieras inga element.

När ett mellanrum (även kallt tomt kortplats) nås i en gles array, får du också en undefined.

Följande exempel genererar glesa matriser och försöker komma åt sina tomma platser:

sparse1 skapas genom att anropa en Array konstruktör med ett numeriskt första argument.Den har 3 tomma platser.

sparse2 skapas med en matris bokstavlig med det andra elementet som saknas.

I någon av dessa glesa matriser utvärderas åtkomst till en tom plats till undefined.

För att undvika undefined, när du arbetar med matriser, se till att använda giltiga matrisindex och förhindra skapandet av glesa matriser.

4. Skillnad mellan odefinierad och null

Vad är den största skillnaden mellan undefined och null? Båda specialvärdena innebär ett tomt tillstånd.

undefined representerar värdet på en variabel som ännu inte har initierats, medan null representerar en avsiktlig avsaknad av ett objekt.

Låt oss undersöka skillnaden i några exempel.

Variabeln number definieras tilldelas dock inte ett initialvärde:

number variabel är undefined, vilket indikerar en oinitialiserad variabel.

Samma oinitialiserade koncept händer när en icke-existerande objektegenskap nås:

Eftersom lastName egenskap finns inte i obj, JavaScript utvärderar obj.lastName till undefined.

På andra sidan vet du att en variabel förväntar sig ett objekt. Men av någon anledning kan du inte instantiera objektet. I så fall är null en meningsfull indikator på ett saknat objekt.

Till exempel är clone() en funktion som klonar ett vanligt JavaScript-objekt. Funktionen förväntas returnera ett objekt:

Men clone() kan åberopas med ett icke-objektargument: 15 eller null. I ett sådant fall kan funktionen inte skapa en klon, så den returnerar null – indikatorn för ett saknat objekt.

typeof operatör gör skillnad mellan undefined och null:

Även den strikta kvalitetsoperatören === skiljer sig korrekt mellan undefined från null:

5. Slutsats

undefined existens är en konsekvens av JavaScript: s tillåtna natur som tillåter användning av:

  • oinitialiserade variabler
  • icke-existerande objektegenskaper eller metoder
  • utom gränser för att få åtkomst till arrayelement
  • anropsresultatet för en funktion som inte returnerar något

Att jämföra direkt mot undefined är osäkert eftersom du förlitar dig på en tillåten men avskräckt metod som nämns ovan.

En effektiv strategi är att minimera utseendet på undefined nyckelord i din kod genom att tillämpa goda vanor som:

  • minska användningen av oinitialiserade variabler
  • gör variablerna livscykel korta och nära källan till deras användning
  • när så är möjligt tilldela initialvärden till variabler
  • favor const, annars använd let
  • använd standardvärden för obetydliga funktionsparametrar
  • verifiera egenskaperna existens eller fyll de osäkra objekten med standardegenskaper
  • undvik användning av glesa matriser

Är det bra att JavaScript har både undefined och null för att representera tomma värden?

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *