Strings
JavaScript strings are immutable sequences of UTF-16 code units. The principal forms: single-quoted ('...'), double-quoted ("..."), and template literals (backtick-delimited, admitting interpolation ${...} and multi-line content). Strings admit substantial methods through the String prototype: slice, split, replace, match, padStart, padEnd, trim, toUpperCase, toLowerCase, etc. The RegExp admits substantial pattern matching with the conventional regex syntax. For Unicode-aware iteration, for...of and [...string] admit substantial code-point handling. The combination — UTF-16 immutable strings, substantial method library, template literals for interpolation, regex integration, the substantial Unicode handling caveats — is the substance of JavaScript’s text surface.
String literals
Three principal forms:
'single quotes';
"double quotes";
`template literal with ${interpolation} and
multi-line support`;
Single and double quotes are equivalent — admit the same escape sequences:
"line 1\nline 2";
'tab\there';
"quote: \"inner\"";
'single: \'inner\'';
"backslash: \\";
"unicode: é";
"\x41"; // "A" (hex)
"\u{1F600}"; // 😀 (Unicode)
Template literals admit interpolation and multi-line:
const name = "Alice";
const age = 30;
const greeting = `Hello, ${name}!`;
const description = `${name} is ${age} years old`;
const html = `
<div class="card">
<h2>${name}</h2>
<p>Age: ${age}</p>
</div>
`;
// Expressions in interpolation:
const formatted = `Pi: ${Math.PI.toFixed(2)}`;
const result = `Sum: ${1 + 2 + 3}`;
const conditional = `Status: ${active ? "active" : "inactive"}`;
Tagged templates
Template literals admit tag functions:
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (i < values.length ? `<mark>${values[i]}</mark>` : "");
}, "");
}
const name = "Alice", age = 30;
const html = highlight`Name: ${name}, Age: ${age}`;
// "Name: <mark>Alice</mark>, Age: <mark>30</mark>"
The conventional uses are CSS-in-JS libraries (styled-components), SQL builders, internationalisation, and HTML-escaping templates.
The String.raw admits “no escape interpretation”:
String.raw`C:\Users\Alice\file.txt`; // "C:\Users\Alice\file.txt" (literal)
String.raw`\n is two chars`; // "\n is two chars"
String operations
const s = "hello world";
s.length; // 11
s[0]; // "h" (also: s.charAt(0))
s[s.length - 1]; // "d"
s.at(-1); // "d" (modern, admits negative)
s.includes("world"); // true
s.startsWith("hello"); // true
s.endsWith("world"); // true
s.indexOf("o"); // 4
s.lastIndexOf("o"); // 7
s.toUpperCase(); // "HELLO WORLD"
s.toLowerCase(); // "hello world"
s.slice(0, 5); // "hello"
s.slice(-5); // "world" (negative from end)
s.substring(0, 5); // "hello" (similar; treats negative as 0)
s.split(" "); // ["hello", "world"]
s.split(""); // ["h", "e", "l", "l", "o", ...]
s.trim(); // strip both ends
s.trimStart(); // strip start
s.trimEnd();
s.replace("world", "JS"); // "hello JS"
s.replaceAll("l", "L"); // "heLLo worLd"
s.padStart(15, "*"); // "****hello world"
s.padEnd(15, "*"); // "hello world****"
s.repeat(3); // "hello worldhello worldhello world"
s.normalize("NFC"); // Unicode normalisation
Conversion
// Number → String:
String(42); // "42"
(42).toString();
(3.14).toFixed(2); // "3.14"
(255).toString(16); // "ff" (hex)
`${42}`; // template literal coercion
// String → Number:
Number("42"); // 42
Number("3.14"); // 3.14
Number("not a number"); // NaN
Number(""); // 0 (substantial pitfall)
parseInt("42px"); // 42 (parses prefix)
parseInt("0xFF", 16); // 255
parseFloat("3.14abc"); // 3.14
+"42"; // 42 (unary plus)
// Boolean → String:
String(true); // "true"
`${true}`; // "true"
// String → Boolean:
Boolean(""); // false (empty string)
Boolean("anything"); // true (non-empty)
Boolean("false"); // true (non-empty!)
Iteration
const s = "hello";
// Char-by-char (UTF-16 code units):
for (let i = 0; i < s.length; i++) {
console.log(s[i]);
}
// Code points (Unicode-aware):
for (const c of s) {
console.log(c); // h, e, l, l, o
}
// As array:
const chars = [...s];
const chars = Array.from(s);
For substantial Unicode (emoji, supplementary characters), for...of and the spread admit code-point iteration; raw indexing admits code-unit iteration:
const emoji = "😀";
emoji.length; // 2 (UTF-16 surrogate pair)
[...emoji].length; // 1 (one code point)
emoji[0]; // half of the surrogate pair
[...emoji][0]; // "😀"
Regular expressions
JavaScript admits regex via literal syntax and the RegExp constructor:
const re = /pattern/flags;
const re = new RegExp("pattern", "flags");
// Common flags:
const ci = /hello/i; // case-insensitive
const global = /hello/g; // all matches
const multi = /^hello/m; // multi-line
const dotall = /./s; // . matches newline
const unicode = /./u; // Unicode-aware
const sticky = /hello/y; // sticky (lastIndex)
match, matchAll, test, exec
"hello world".match(/o/g); // ["o", "o"]
"hello".match(/(\w+)/); // ["hello", "hello", index: 0, ...]
// All matches with groups (ES2020):
const matches = [..."abc 123 def 456".matchAll(/(\d+)/g)];
// [{0: "123", 1: "123", index: 4, ...}, {0: "456", ...}]
/^[a-z]+$/.test("hello"); // true (boolean)
/(\w+)/.exec("hello world"); // matches like .match(re) (without g)
Captures
const date = "2026-01-15";
const m = date.match(/(\d{4})-(\d{2})-(\d{2})/);
m[1]; // "2026"
m[2]; // "01"
m[3]; // "15"
// Named groups:
const m2 = date.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
m2.groups.year; // "2026"
m2.groups.month; // "01"
replace
"hello world".replace("world", "JS"); // "hello JS"
"hello".replace(/l/g, "L"); // "heLLo"
"hello".replaceAll("l", "L"); // "heLLo" (modern)
// With function:
"abc 123".replace(/\d+/, (match) => match * 2); // "abc 246"
// With backreferences:
"John Smith".replace(/(\w+) (\w+)/, "$2 $1"); // "Smith John"
// With named groups:
"John Smith".replace(/(?<first>\w+) (?<last>\w+)/, "$<last>, $<first>");
// "Smith, John"
split with regex
"a,b;c|d".split(/[,;|]/); // ["a", "b", "c", "d"]
" hello world ".split(/\s+/); // ["", "hello", "world", ""]
"hello".split(/(?=)/); // ["h", "e", "l", "l", "o"]
Common patterns
Building substantial strings
// Inefficient (O(n²)):
let s = "";
for (const item of items) {
s += item.toString() + "\n";
}
// Efficient with array.join (O(n)):
const s = items.map(String).join("\n");
// Or with reduce (rare):
const s = items.reduce((acc, item) => acc + item + "\n", "");
Multi-line literal
const sql = `
SELECT id, name, email
FROM users
WHERE active = true
ORDER BY created_at DESC
`.trim();
const html = `
<article>
<h2>${title}</h2>
<p>${content}</p>
</article>
`.trim();
Validation
function isEmail(s) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
}
function isUUID(s) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s);
}
function isNumeric(s) {
return /^-?\d+(\.\d+)?$/.test(s);
}
Trimming
" hello ".trim(); // "hello"
" hello ".trimStart(); // "hello "
" hello ".trimEnd(); // " hello"
// Custom (regex):
"---hello---".replace(/^-+|-+$/g, ""); // "hello"
Padding
String(42).padStart(5, "0"); // "00042"
String("Alice").padEnd(20, " "); // "Alice "
// Number formatting:
const formatted = `$${(1234.5).toFixed(2)}`; // "$1234.50"
Slugify
function slugify(s) {
return s
.toLowerCase()
.normalize("NFD")
.replace(/[̀-ͯ]/g, "") // strip accents
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "");
}
slugify("Hello, World!"); // "hello-world"
slugify("Café & Résumé"); // "cafe-resume"
Format with placeholders
function format(template, values) {
return template.replace(/\$\{(\w+)\}/g, (_, key) => values[key] ?? "");
}
format("Hello, ${name}!", { name: "Alice" }); // "Hello, Alice!"
For more substantial interpolation, the template literal itself is conventionally clearer.
Parse query string
const url = new URL("https://example.com?name=Alice&age=30");
url.searchParams.get("name"); // "Alice"
url.searchParams.get("age"); // "30"
[...url.searchParams.entries()]; // [["name", "Alice"], ["age", "30"]]
// Build:
const params = new URLSearchParams({ name: "Alice", age: 30 });
params.toString(); // "name=Alice&age=30"
The URL and URLSearchParams admit substantial URL handling.
Escape HTML
function escapeHtml(s) {
return s
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// Or with a tagged template:
function html(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (i < values.length ? escapeHtml(String(values[i])) : "");
}, "");
}
const safe = html`<p>Hello, ${userInput}!</p>`;
Capitalisation
function capitalize(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
function titleCase(s) {
return s.replace(/\w\S*/g, (w) =>
w.charAt(0).toUpperCase() + w.substring(1).toLowerCase()
);
}
capitalize("hello"); // "Hello"
titleCase("hello world"); // "Hello World"
Reverse
[..."hello"].reverse().join(""); // "olleh"
// For substantial Unicode (preserves grapheme clusters partially):
"héllo".split("").reverse().join(""); // pitfalls with combining marks
The “true” string reversal is substantial — multi-codepoint graphemes (emoji families, etc.) require Intl.Segmenter:
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
const segments = [...segmenter.segment("👨👩👧")];
const reversed = segments.map(s => s.segment).reverse().join("");
Truncate
function truncate(s, maxLen, suffix = "...") {
if (s.length <= maxLen) return s;
return s.slice(0, maxLen - suffix.length) + suffix;
}
truncate("Hello, world!", 10); // "Hello, ..."
Intl for substantial formatting
new Intl.NumberFormat("en-US").format(1234567.89);
// "1,234,567.89"
new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(1234.56);
// "$1,234.56"
new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric"
}).format(new Date());
// "January 15, 2026"
new Intl.RelativeTimeFormat("en", { numeric: "auto" }).format(-1, "day");
// "yesterday"
new Intl.ListFormat("en", { style: "long" }).format(["Alice", "Bob", "Charlie"]);
// "Alice, Bob, and Charlie"
The Intl admits substantial internationalisation.
A note on Unicode
JavaScript strings are UTF-16 — substantial complexity for emoji and supplementary characters:
const emoji = "😀";
emoji.length; // 2 (surrogate pair)
emoji.charAt(0); // "\uD83D" (high surrogate)
[...emoji].length; // 1 (correct count)
[...emoji][0]; // "😀" (full character)
// Substantial grapheme clusters (combining characters):
const family = "👨👩👧"; // 3 people joined by ZWJ
[...family].length; // multiple code points
The conventional defences:
for...ofand[...string]— code-point iteration.String.prototype.normalize()— canonical equivalence.Intl.Segmenter— grapheme-cluster iteration (modern).
A note on the conventional discipline
The contemporary JavaScript strings advice:
- Use template literals for interpolation.
- Use single quotes or backticks — community split; consistent within a project.
- Use
string.includesoverindexOf !== -1. - Use
replaceAlloverreplace(/.../g, ...)for plain string replacement. - Use
sliceoversubstring(negative indices admitted). - Use
toFixed/Intl.NumberFormatfor number formatting. - Use
Intl.*for internationalisation. - Use
for...ofor spread for Unicode-aware iteration. - Use template literals over
+for string concatenation. - Use
array.joinover+=in loops. - Use named regex groups for substantial parsing.
The combination — UTF-16 immutable strings, the principal literal forms (single, double, template), the substantial method library, regex integration, the Intl substantial internationalisation, the Unicode handling caveats — is the substance of JavaScript’s text surface. The discipline produces concise, expressive text manipulation with substantial care required for substantial Unicode correctness.