`String`-objekt

Den här artikeln förklarar String-objektet.

Förklaringen täcker allt från grunder till avancerade tekniker, inklusive fallgropar relaterade till Unicode och reguljära uttryck, steg för steg och på ett lättförståeligt sätt.

YouTube Video

String-objekt

Strängar i JavaScript är en av de mest använda typerna i vardaglig utveckling.

Skillnad mellan primitiva strängar och strängobjekt

Primitiva strängar (som "hello") beter sig annorlunda än wrapper-objekt som new String("hello"). Vanligtvis bör du använda primitivt värde, och det finns sällan behov av att använda objektformen.

1// Primitive string
2const a = "hello";
3
4// String wrapper object
5const b = new String("hello");
6
7console.log(typeof a); // "string"
8console.log(typeof b); // "object"
9console.log(a === b);  // false — wrapper objects are not strictly equal
  • Den här koden visar skillnaden i typ mellan en primitiv och en wrapper, samt hur de beter sig vid strikt jämförelse. I de flesta fall bör du undvika att använda new String() och hålla dig till primitiva värden.

Sätt att skapa strängar (literaler och mallliteraler)

Mallliteraler är användbara för att bädda in variabler och skriva flerradiga strängar. Du kan intuitivt infoga variabler och utvärdera uttryck.

1const name = "Alice";
2const age = 30;
3
4// Template literal
5const greeting = `Name: ${name}, Age: ${age + 1}`;
6
7console.log(greeting); // "Name: Alice, Age: 31"
  • Mallliteraler är mycket läsbara och idealiska för att bygga komplexa strängar, inklusive flerradiga strängar.

Vanliga metoder (sökning och extrahering av delsträngar)

String-objektet har många grundläggande metoder.

 1const text = "Hello, world! Hello again.";
 2
 3// search
 4console.log(text.indexOf("Hello"));       // 0
 5console.log(text.indexOf("Hello", 1));    // 13
 6console.log(text.includes("world"));      // true
 7console.log(text.startsWith("Hello"));    // true
 8console.log(text.endsWith("again."));     // true
 9
10// slice / substring
11console.log(text.slice(7, 12));           // "world"
12console.log(text.substring(7, 12));       // "world"
  • slice och substring är lika, men de hanterar negativa index på olika sätt. slice tolkar negativa värden som positioner bakifrån. Var tydlig med vilken du ska använda.

Dela upp och sammanfoga (split / join)

Det är vanligt att dela upp en sträng till en array för bearbetning och sedan foga ihop den igen.

1const csv = "red,green,blue";
2const arr = csv.split(","); // ["red","green","blue"]
3
4console.log(arr);
5console.log(arr.join(" | ")); // "red | green | blue"
  • Ett vanligt mönster är att använda split för att dela en sträng, bearbeta den resulterande arrayen med map eller filter, och sedan använda join för att foga ihop den igen.

Ersätta och reguljära uttryck

replace ersätter endast första träffen. Om du vill ersätta alla träffar, använd flaggan g med ett reguljärt uttryck. Genom att skicka en funktion som ersättning kan du också utföra dynamiska utbyten.

 1const s = "foo 1 foo 2";
 2
 3// replace first only
 4console.log(s.replace("foo", "bar")); // "bar 1 foo 2"
 5
 6// replace all using regex
 7console.log(s.replace(/foo/g, "bar")); // "bar 1 bar 2"
 8
 9// replace with function
10const r = s.replace(/\d+/g, (match) => String(Number(match) * 10));
11console.log(r);    // "foo 10 foo 20"
  • Med dynamisk ersättning via funktion kan du skriva kort kod som analyserar och transformerar träffar.

Versalförändring och normalisering

För flerspråkigt stöd och jämförelse är det, förutom toLowerCase och toUpperCase, även viktigt med Unicode-normalisering (normalize). Detta är särskilt nödvändigt vid jämförelse av bokstäver med accent.

 1// Case conversion example:
 2// "\u00DF" represents the German letter "ß".
 3// In some locales, converting "ß" to uppercase becomes "SS".
 4// JavaScript follows this behavior.
 5console.log("\u00DF");
 6console.log("\u00DF".toUpperCase()); // "SS"
 7
 8// Unicode normalization example:
 9// "e\u0301" is "e" + a combining acute accent.
10// "\u00e9" is the precomposed character "é".
11// These two look the same but are different code point sequences.
12const a = "e\u0301";
13const b = "\u00e9";
14
15console.log(a === b);   // false: different underlying code points
16console.log(a.normalize() === b.normalize()); // true: normalized to the same form
  • Olika Unicode-representationer, såsom ligaturer och kombinerade tecken, är inte lika som de är, så använd normalize() innan du jämför.

Unicode och kodpunkter (hantera surrogatpar)

JavaScript-strängar är sekvenser av UTF-16-kodenheter, så vissa tecken som emojis kan ta upp två enheter per tecken. För att hantera riktiga teckenenheter, använd Array.from, spridningsoperatorn eller for...of.

 1// Emoji composed with multiple code points:
 2// "\u{1F469}" = woman, "\u{200D}" = Zero Width Joiner (ZWJ),
 3// "\u{1F4BB}" = laptop. Combined, they form a single emoji: 👩‍💻
 4const s = "\u{1F469}\u{200D}\u{1F4BB}";
 5console.log(s);
 6
 7// Length in UTF-16 code units (not actual Unicode characters):
 8// Because this emoji uses surrogate pairs + ZWJ, the length may be > 1.
 9console.log("Length:", s.length);
10
11// Iterate by Unicode code points (ES6 for...of iterates code points):
12// Each iteration gives a full Unicode character, not UTF-16 units.
13for (const ch of s) {
14  console.log(ch);
15}
16
17// Convert to an array of Unicode characters:
18console.log(Array.from(s));
  • length returnerar antalet kodenheter, så du får kanske inte förväntat antal med emojis eller ligaturer. for...of och Array.from hanterar något som liknar visade tecken (grafemkluster), men om du behöver fullständigt grafemstöd, överväg att använda ett specialiserat bibliotek.

Säker ersättning med reguljära uttryck (vid hantering av användarinmatning)

Om du glömmer att rymma användarinmatning när du infogar den i ett reguljärt uttryck kan det leda till oväntat beteende och sårbarheter. Escape:a alltid användarinmatning innan du använder den i ett mönster.

1function escapeRegex(s) {
2  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3}
4
5const userInput = "a+b";
6const pattern = new RegExp(escapeRegex(userInput), "g");
7console.log("a+b a+b".replace(pattern, "X")); // "X X"
  • Använd aldrig användarsträngar direkt i reguljära uttryck; escape:a dem alltid innan du skapar regexet.

Prestandatips: Sammanfogning och mallar

Vid sammanfogning av många små strängar i följd kan det vara effektivare att lägga dem i en array och använda join. Å andra sidan är mallsträngar mycket läsbara och tillräckligt snabba i de flesta fall.

 1// concatenation in loop (less ideal)
 2let s = "";
 3for (let i = 0; i < 1000; i++) {
 4  s += i + ",";
 5}
 6
 7// using array + join (often faster for many pieces)
 8const parts = [];
 9for (let i = 0; i < 1000; i++) {
10  parts.push(i + ",");
11}
12const s2 = parts.join("");
  • Moderna JavaScript-motorer är mycket optimerade, så du behöver inte oroa dig för prestanda vid ett mindre antal sammanfogningar. Men om du behöver sammanfoga tiotusentals gånger kan användning av join vara effektivare.

Praktiskt användbara tekniker: Padding, trim och repeat

trim, padStart, padEnd och repeat är praktiska metoder som är särskilt användbara vid vardaglig stränghantering. De används ofta i praktiska situationer som att formatera indata eller standardisera utdataformat.

1console.log("  hello  ".trim());       // "hello"
2console.log("5".padStart(3, "0"));     // "005"
3console.log("x".repeat(5));            // "xxxxx"
  • Dessa metoder kan användas för att normalisera formulärinmatning eller skapa fast bredd på utdata.

Strängjämförelse (lokalanpassad jämförelse)

localeCompare är effektivt för att jämföra strängar enligt ordboksordning för olika språk. Du kan ange språk och känslighetsalternativ (som skiftlägeskänslighet).

1console.log(
2  "\u00E4".localeCompare("z", "de")
3); // may be -1 or other depending on locale
4
5console.log(
6  "a".localeCompare("A", undefined, { sensitivity: "base" })
7); // 0
  • För internationaliserade jämförelser, använd localeCompare och ange lämplig locale och alternativ.

Praktiskt exempel: Konvertera en CSV-rad till ett objekt (praktiskt arbetsflöde)

Ett vanligt användningsfall är att tolka en enda CSV-rad till ett objekt med hjälp av split, trim och map. För citerade fält eller komplexa CSV-filer, använd en dedikerad CSV-tolk.

 1// simple CSV parse (no quotes handling)
 2function parseCsvLine(line, headers) {
 3  const values = line.split(",").map(v => v.trim());
 4  const obj = {};
 5  headers.forEach((h, i) => obj[h] = values[i] ?? null);
 6  return obj;
 7}
 8
 9const headers = ["name", "age", "city"];
10const line = " Alice , 30 , New York ";
11console.log(parseCsvLine(line, headers));
12// { name: "Alice", age: "30", city: "New York" }
  • Denna metod fungerar för enkel CSV, men tänk på att den inte kan hantera fall där ett komma finns inuti ett citerat fält.

Vanliga fallgropar

Det finns några lättförbisedda specifikationer och beteenden vid stränghantering i JavaScript. För att undvika oväntade buggar är det viktigt att tänka på följande.

  • Att använda new String() kan leda till felaktiga resultat vid typkontroller eller jämförelser. I de flesta fall räcker de primitiva strängtyperna.
  • I Unicode kan ett enskilt synligt tecken bestå av flera kodenheter. Därför kanske värdet som returneras av length inte motsvarar det faktiska antalet visade tecken.
  • När du inkluderar användarinmatning i ett reguljärt uttryck, se alltid till att escape:a det först.
  • String.prototype.replace() ersätter endast den första träffen som standard. Om du vill ersätta alla förekomster, använd flaggan /g i ditt reguljära uttryck.
  • Strängar är oföränderliga, så operationer returnerar alltid en ny sträng. Det är viktigt att alltid tilldela det returnerade värdet.

Sammanfattning

Även om JavaScript-strängar kan verka enkla är det viktigt att förstå deras egenskaper vad gäller Unicode och oföränderlighet. Genom att behärska grunderna kan du avsevärt förbättra tillförlitligheten och läsbarheten i din stränghantering.

Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.

YouTube Video