Lesson 19 / 21
Strings
Template literals, slicing and search methods, locale-dependent case conversion, the code-unit/grapheme distinction, and number formatting.
Contents
Up to this lesson, output was produced with console.log arguments. Writing report
text requires strings’ own tools. This lesson defines those tools and finishes a subject
left open in the Primitive Types lesson — the practical consequence of a string being a
sequence of code units.
Strings are immutable; every method here produces a new string and does not change the original value.
Template Literals
Strings written with backticks carry three extra abilities: expression interpolation, being multi-line, and different escaping rules.
const record = { station: "A1", temperature: 21.5 }; const threshold = 20; const template = `${record.station}: ${record.temperature} C (${record.temperature > threshold ? "above threshold" : "below threshold"})`; console.log(template); const multiLine = `station: ${record.station} temperature: ${record.temperature}`; console.log(multiLine); console.log(`quote: "A1", backtick: \`A1\`, dollar: \${record}`);
A1: 21.5 C (above threshold)
station: A1
temperature: 21.5
quote: "A1", backtick: `A1`, dollar: ${record}
What gets interpolated is an expression; conditions are written with the ternary
operator from the Conditions and Selection lesson. Every expression is converted to
text — the ToString procedure from the Type Conversion and Coercion lesson applies — so
interpolating an object produces [object Object].
A line break is written directly; no escape sequence is needed. A backslash is used to escape the backtick and the dollar–brace pair.
In this course, text concatenation is written with template literals. The reason is to
avoid the + operator’s two-faced behavior seen in the Type Conversion and Coercion
lesson: with a template, the syntax itself makes it clear the result is text.
Slicing and Splitting
const line = "A1;21.5;48"; console.log(line.length, line[0], line.at(-1)); console.log(line.slice(0, 2), line.slice(-2)); console.log(line.slice(4, 2), line.substring(4, 2)); console.log(line.split(";")); console.log(line.split(";", 2)); console.log(["A1", "21.5"].join(";"));
10 A 8 A1 48 ;2 [ 'A1', '21.5', '48' ] [ 'A1', '21.5' ] A1;21.5
The third line shows the difference between the two slicing methods. slice accepts a
negative index and gives an empty string when the start is greater than the end.
substring counts negative indices as zero and swaps its arguments if needed; this
is why it produced ;2. The two results being written on the same line, separated by a
space, shows the first is empty. This course uses slice: its behavior matches array
slice and produces no surprises.
split splits by a separator, and a second argument limits the result count. The limit
does not append the remaining text to the last piece — it discards it, a common
source of unexpected data loss when parsing a file.
Search and Replace
const line = "station=A1;field=temperature;field=humidity"; console.log(line.includes("field"), line.indexOf("field"), line.lastIndexOf("field")); console.log(line.startsWith("station"), line.endsWith("humidity")); console.log(line.replace("field", "FIELD")); console.log(line.replaceAll("field", "FIELD")); console.log(" A1 ".trim() + "|", " A1 ".trimStart() + "|"); console.log("A1".padStart(5, "0"), "A1".padEnd(5, "."), "-".repeat(10));
true 11 29 true true station=A1;FIELD=temperature;field=humidity station=A1;FIELD=temperature;FIELD=humidity A1| A1 | 000A1 A1... ----------
When called with a text argument, replace changes only the first match;
replaceAll is used for all of them. This is a distinction that produces silent bugs.
Padding methods are the basis of producing aligned output, and a padding character can be given. Trimming methods drop leading and trailing whitespace; which characters count as whitespace is defined in the standard.
Locale-Dependent Case Conversion
Case conversion is language-dependent, and the default methods apply a language-independent rule:
const name = "station"; console.log(name.toUpperCase()); console.log(name.toLocaleUpperCase("tr")); console.log("ISTANBUL".toLowerCase()); console.log("ISTANBUL".toLocaleLowerCase("tr"));
STATION STATİON istanbul ıstanbul
The difference shows up in languages where dotted and dotless i are separate
letters. The default conversion turns i into the dotless capital; the locale-aware
conversion produces the dotted capital.
Two rules follow from this. If text is being converted to show to a user, the locale should be specified. In contrast, a conversion done for comparison should not use the locale-aware form: the same text gives different results in different locales, and the comparison becomes inconsistent. Key comparisons are done with the language-independent form.
Code Unit and Grapheme
The Primitive Types lesson said a string is a sequence of code units. The consequence shows up in slicing:
const text = "A1😀"; console.log(text.length); console.log([...text].length); console.log([...text]); console.log(Array.from(text).length);
4 3 [ 'A', '1', '😀' ] 3
length gave four because the last character consists of two code units — the
surrogate pair from the How Computers Work course. Spread and Array.from walk the
string by code points and give three elements.
Cutting at a code-unit boundary corrupts the text:
const text = "A1😀"; console.log(text.slice(0, 3).length); console.log([...text].slice(0, 3).join(""));
3 A1😀
The first slice took only the first of the double code unit; the result is a code unit with no meaning on its own, and it looks broken when displayed. The second way did not split the character, because it operates on the sequence of code points.
A grapheme — the unit a user counts as a single character — can be larger than a code point too; combining marks are an example:
const combined = "é"; const single = "é"; console.log(combined.length, single.length, combined === single); console.log(combined.normalize("NFC") === single);
2 1 false true
The two strings look identical but are different sequences of code points; comparison gives the wrong answer. Normalization brings both to a common form. Code that compares text should normalize its inputs first.
Number Formatting
console.log((21.456).toFixed(2)); console.log((0.1 + 0.2).toFixed(2)); console.log((1.005).toFixed(2)); console.log((21.5).toString(2)); console.log(String(1e21), String(1e-7));
21.46 0.30 1.00 10101.1 1e+21 1e-7
toFixed rounds to the given digit count and returns text. The third line looks
like a surprise, but it is a consequence of the model from the Primitive Types lesson:
the value 1.005 cannot be represented exactly in floating-point, and the stored value
is slightly less than 1.005, so it rounds down. The rounding error is not in the
formatting, it is in the number itself.
toString takes a base argument; the representation from the Binary Number System
lesson of the How Computers Work course is produced this way. The last line shows very
large and very small numbers switching to exponential notation; the thresholds are
defined in the standard.
Writing the Measurement Report
When this lesson’s tools combine, an aligned report comes out:
const records = [ { station: "A1", temperature: 21.5, humidity: 48 }, { station: "A2", temperature: 19.75, humidity: 55 }, { station: "B1", temperature: 23, humidity: null }, ]; const header = `${"station".padEnd(10)}${"temperature".padStart(12)}${"humidity".padStart(9)}`; const line = "-".repeat(header.length); const rows = records.map((r) => { const humidityText = r.humidity == null ? "-" : String(r.humidity); return `${r.station.padEnd(10)}${r.temperature.toFixed(2).padStart(12)}${humidityText.padStart(9)}`; }); console.log([header, line, ...rows].join("\n"));
station temperature humidity ------------------------------- A1 21.50 48 A2 19.75 55 B1 23.00 -
The missing humidity field was shown with -; the == null check is the exception
from the Equality Comparisons lesson. toFixed(2) brought every temperature to the
same digit count — the value 23 was written as 23.00. The rows were joined into a
single text with join and printed in one call.
Alignment is based on code-unit count. If the report contains characters outside the Latin set, the padding methods balance code-unit count, not visual width, and the columns can drift.
Summary
- Template literals interpolate expressions, are multi-line, and are this course’s only form of text concatenation.
sliceaccepts a negative index and gives an empty string on a reversed range;substringswaps its arguments.replacechanges only the first match;replaceAllis needed for all of them.- Case conversion is locale-dependent; use the locale-aware form for display, the language-independent form for comparison.
lengthcounts code units; use spread orArray.fromfor code-point-based operations, and normalize before comparing.toFixedreturns text, and rounding is affected by the number’s floating-point representation.
Next Step
The report produced readable text, but not a format the machine can read back. The next lesson takes up JSON: serialization and parsing, values the format does not support, the error a circular reference produces, and callbacks that adapt the conversion. The serialization problem left open in the BigInt lesson will also be finished there.
To keep your progress and take notes, Log in
My notes
Log in to take notes.