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 avarray
-
last
: element infogat i slutet avarray
.
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 medundefined
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 objektoptions
. Det nya objektet tar emot alla egenskaper frånunsafeOptions
, men de saknade hämtas fråndefaults
.
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 returnerarundefined
.
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, medannull
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ändlet
- 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?