Skip to content
academia.sh

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.
  • slice accepts a negative index and gives an empty string on a reversed range; substring swaps its arguments.
  • replace changes only the first match; replaceAll is needed for all of them.
  • Case conversion is locale-dependent; use the locale-aware form for display, the language-independent form for comparison.
  • length counts code units; use spread or Array.from for code-point-based operations, and normalize before comparing.
  • toFixed returns 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.

Start typing to search.

↑↓ Esc navigate · open · close