Strings and Template Literals
Building text, template literals over concatenation, the string methods worth knowing, and why every string operation gives you a new string.
Building text, template literals over concatenation, the string methods worth knowing, and why every string operation gives you a new string.
Nearly everything a program touches is text. What someone types into a form, what is in a file, what arrives from another service, what you show on a screen — all of it text, all of it needing to be joined, cut apart, cleaned up and reassembled into something a person can read.
By the end of this lesson you will build messages readably, know the operations that come up constantly, and understand why a string can never be changed once it exists — which is the source of one specific mistake that catches everybody exactly once.
A string is a piece of text, written in quotes:
const photographer = "Ana Duarte";
const city = 'Lisbon';Single and double quotes behave identically. Having both is useful when the text contains one of them:
const caption = "Ana's first exhibition";
const quote = 'She said "yes" immediately';If you need the same kind of quote inside, put a backslash before it — that means "treat the next character as text":
const awkward = "She said \"yes\" to Ana's request";The same backslash gives you \n for a line break and \t for
a tab.
You can join strings with +:
const message = photographer + " uploaded " + count + " photos";It works, and it is hard to read, easy to get wrong — one
missing space gives Ana Duarteuploaded — and it gets worse
with every value added.
The better tool is a template literal, written with
backticks. Values go inside ${...}:
const message = `${photographer} uploaded ${count} photos`;
console.log(message); // Ana Duarte uploaded 400 photosYou supply every space by hand.
One missing space gives Ana Duarteuploaded, and the mistake
is invisible in the source.
It gets worse with every value you add.
Write the sentence, drop the values in.
Spaces are where you typed them, because you typed the sentence. Numbers convert themselves.
Backticks also span lines, keeping the breaks exactly.
Anything can go in the braces, not only a name:
console.log(`${count} photos, ${(count / 24).toFixed(1)} per hour`);
// 400 photos, 16.7 per hourAnd backticks span lines, keeping the breaks exactly:
const description = `Taken at dawn on the Praça do Comércio,
before the tram queues formed.`;Note the backtick is not the same key as an apostrophe. It is usually top-left on the keyboard, and typing the wrong one gives a confusing error about an unterminated string.
A string is a sequence of characters, numbered from zero:
Starting at zero feels arbitrary for about a week and then
becomes invisible. Note that length is a property with no
parentheses, while most of what follows are methods with them —
a distinction the editor will remind you of.
Taking a range of characters is slicing:
The "up to but not including" rule is worth internalising:
slice(0, 3) gives three characters, numbered 0, 1 and 2. The
payoff is that slice(0, 3) and slice(3) split the string
cleanly, with the same number ending one and starting the other.
Asking for a position that does not exist gives undefined
rather than an error, which is worth knowing because it means a
mistake here does not announce itself.
trim() earns its place immediately. Text from forms and files
arrives with trailing spaces and invisible line-break characters
constantly, and two strings that look identical on screen
compare as different when one has a stray space.
Cutting apart and putting back together:
split cuts at every occurrence of what you give it and hands
back a list. join is its reverse, called on the list with the
separator as its argument.
Searching and replacing:
Two traps in that list, and both produce wrong answers rather than errors.
replace changes only the first one.
"a_b_c".replace("_", " ") gives "a b_c".
Almost never what you want. Reach for replaceAll by
default.
indexOf returns -1 when absent.
So if (title.indexOf("x")) is a bug — -1 counts as true,
and the condition passes precisely when there is no match.
Use includes whenever you want a yes or no.
Padding, for lining things up:
Strings are immutable: once one exists, nothing alters it. Every operation that looks like a change produces a new string.
This catches everyone once. toUpperCase() did its job
perfectly and handed back "LISBON", and nobody kept it.
Bad — calling the method and discarding the answer.
Good — keeping what comes back.
Nothing errors in the first version. The methods run, produce correct results, and drop them — so the record is saved with its leading spaces and its line break, and it will never match the same name typed cleanly. You find out weeks later, from a duplicate customer that is not a duplicate.
The rule: a string method always returns; it never changes. Assign the result, or chain the calls and assign at the end.
One more thing, briefly, because it explains a family of surprises.
Strings hold characters, not letters of the English
alphabet. "Praça", "日本" and "🇵🇹" are all ordinary
strings.
length counts storage units rather than what a person would
call characters, so an emoji or an accented character built from
two parts can report a length of 2 or 4. For counting, comparing
and slicing anything that might not be plain English, prefer
working with whole words or use the tools designed for it:
You will not need this often. Knowing it exists means that when
length reports something surprising, you recognise it as a
known property of text rather than a bug in your code.
You can now build readable messages, cut text apart and put it back together, clean up what forms and files hand you, and you know why a method call that looks like a change is not one. That immutability rule returns in the arrays lesson, where the opposite is true and the contrast is the point.
Next is Numbers and Floating Point, the other type you will
handle constantly. It explains why one number type covers
everything, why 0.1 + 0.2 does not equal 0.3, and what to
reach for when you are counting money — which is the lesson the
opening bug of this course was really about.
Before you move on, take a line of text with a real shape — a filename, a comma-separated row, a full name — and pull it apart into pieces, then rebuild it in a different format. Splitting, trimming and rejoining is the most common thing you will ever do to text, and ten minutes of it now makes the rest automatic.
WRITING TEXT
"double" 'single' identical; pick to avoid escaping
`backticks` template literal - spans lines
${value} drops a value in <- the default
\" \n \t escaped quote, line break, tab
POSITIONS
s[0] first character
s.length a property - no parentheses
s.slice(0, 3) from 0, UP TO but not including 3
s.slice(3) s.slice(-3) from there; the last three
out of range gives undefined, not an error
METHODS - every one returns a NEW string
s.trim() whitespace off both ends
s.toUpperCase() s.toLowerCase()
s.split(",") -> a list
list.join(" | ") called on the LIST
s.includes(x) yes/no <- prefer this
s.startsWith(x) s.endsWith(x)
s.indexOf(x) position, or -1 - so `if (indexOf)` is a bug
s.replace(a, b) the FIRST occurrence only
s.replaceAll(a, b) all of them <- usually this
s.padStart(3, "0") s.padEnd(10, " ")
THE ONE THAT CATCHES EVERYONE
strings are immutable
s.trim() changes nothing - keep the result
s = s.trim().toUpperCase();
CHARACTERS
length counts storage units, not what a person sees
emoji and combined accents can report 2 or 4
[...s] splits into characters; localeCompare for orderingconst city = "Lisbon";
console.log(city[0]); // L
console.log(city[3]); // b
console.log(city.length); // 6console.log(city.slice(0, 3)); // Lis - from 0, UP TO 3
console.log(city.slice(3)); // bon - from 3 to the end
console.log(city.slice(-3)); // bon - the last threeconst messy = " Ana Duarte\n";
messy.trim(); // "Ana Duarte"
messy.trim().toUpperCase(); // "ANA DUARTE"
"Lisbon".toLowerCase(); // "lisbon"const row = "Ana Duarte,Lisbon,400";
const fields = row.split(","); // ["Ana Duarte", "Lisbon", "400"]
console.log(fields[1]); // Lisbon
console.log(fields.join(" | ")); // Ana Duarte | Lisbon | 400const title = "sunrise_over_lisbon.jpg";
title.includes("lisbon"); // true
title.startsWith("sunrise"); // true
title.endsWith(".jpg"); // true
title.indexOf("over"); // 8, or -1 if absent
title.replace("_", " "); // only the FIRST one
title.replaceAll("_", " "); // all of them"7".padStart(3, "0"); // "007"
"Ana".padEnd(10, " "); // "Ana "const city = "lisbon";
city.toUpperCase();
console.log(city); // lisbon - unchangedlet name = readFromForm();
name.trim();
name.toUpperCase();
saveCustomer(name); // still " ana duarte\n"let name = readFromForm();
name = name.trim().toUpperCase();
saveCustomer(name); // "ANA DUARTE"[..."Praça"].length; // splits into characters
"praça".localeCompare("pracb"); // locale-aware ordering