Topic 4.1
String Methods
In one line
Strings come with built-in methods to cut out pieces (slice), search (indexOf, includes, startsWith), split and join, replace text, trim spaces, pad to a width, repeat and read from the end (at). None of them change the original string: each returns a new one.
Think of it like this
A long paper ribbon with letters printed on it, one letter per square, and the squares numbered from 0. You can't erase or change the ribbon (it is printed in ink). But you can photocopy part of it (slice), look for a word on it (indexOf), cut copies of it at every comma (split), or make a new ribbon with one word swapped (replace). Every tool gives you a new ribbon and leaves the old one exactly as it was.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Method
- A function that belongs to a value and is called with a dot, like
text.trim(). - Immutable
- Can't be changed after it's made. String methods always give back a new string.
- Index
- The position of a character, counting from 0 for the first one.
- Negative index
- A position counted from the end: -1 is the last character.
sliceandataccept these;str[-1]doesn't. - Substring
- A smaller piece of a string, such as "port" inside "report".
- Separator
- The text
splitcuts at (like a comma) orjoinputs between the pieces. - Whitespace
- Characters you can't see that make space: spaces, tabs and new lines.
- Case-sensitive
- Treats capital and small letters as different, so "A" and "a" don't match.
Step by step
01Clean what the user typed
People type extra spaces and random capitals. Before you compare or store text, tidy it. Notice that each method returns a new string, so we keep chaining: each .method() works on the result of the one before.
const typed = " Asha.Rao@Example.COM ";
const email = typed.trim().toLowerCase();
console.log(email); // asha.rao@example.com
console.log(typed); // unchanged: the original still has the spaces02Find where something is
indexOf tells you the position of the @. Everything before it is the user name, everything after it is the domain. slice cuts those pieces out.
const at = email.indexOf("@"); // 8
const user = email.slice(0, at); // "asha.rao"
const domain = email.slice(at + 1); // "example.com"03Ask yes-or-no questions
When you only need to know whether something is there, includes, startsWith and endsWith say it directly. They return booleans, ready for an if.
if (domain.endsWith(".com")) console.log("A .com address");
if (!email.includes("@")) console.log("That is not an email");04Split, change, join
To work on the parts of a string, split it into an array, change the array, and join it back. Here we turn "asha.rao" into the display name "Asha Rao".
const name = user
.split(".") // [ 'asha', 'rao' ]
.map((w) => w[0].toUpperCase() + w.slice(1)) // [ 'Asha', 'Rao' ]
.join(" "); // "Asha Rao"05Shape it for display
padStart lines numbers up and adds leading zeros, padEnd fills a column, and repeat draws a line. Fixed-width text like this is handy for receipts and logs.
console.log("Order #" + String(42).padStart(5, "0")); // Order #00042
console.log("-".repeat(20));Try it yourself
- 1
Predict slice with negatives
In the first example, add
console.log(file.slice(-9, -4));. Count from the end on paper first: which piece comes out? Then run it. - 2
Ignore case properly
Still in the first example,
file.includes("photos")printedfalse. Change it so it finds the word whatever the capitals are, without changing the word you search for. (Hint: lower-case one copy offile.) - 3
Change the receipt width
In the third example, change
padEnd(10, ".")topadEnd(14, ".")and therepeat(18)lines to match. Predict how many dotsTeagets now.
Code & diagrams
`lastIndexOf` finds the last dot, so a name with several dots still splits correctly. `substring(4, 0)` swapped its arguments; `slice(4, 0)` returned an empty string.
Preview
Console
Nothing logged yet.Expected console output
Holiday Photos.final
jpg
JPG G undefined
14 -1
true false
true false
true
[Holi] []In replacement text `$$` means one `$`. A replacement function's return value is used exactly as it is, with no `$` rules. Splitting an empty string gives an array with one empty string, not an empty array.
Preview
Console
Nothing logged yet.Expected console output
[ 'red', 'green', 'blue', 'yellow' ]
red | green | blue | yellow
[ 'a', 'b' ]
[ '' ] [ 'h', 'i' ]
one dog, two cats
one dog, two dogs
Cost: $10
Cost: $$10
snake case name`padStart` never cuts a string that is already long enough, and it repeats or cuts the fill text to fit exactly.
Preview
Console
Nothing logged yet.Expected console output
Tea....... 60
Bill no. 0007
[hi ] [ hi]
ab5 abcBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forget to keep the result
Write let name = " Asha "; name.trim(); console.log("[" + name + "]");.
Break #2
Test indexOf as a boolean
Write if ("apple pie".indexOf("apple")) console.log("found");.
Break #3
Call a string method on something that isn't a string
Write const age = 7; console.log(age.padStart(2, "0"));.
Myth vs fact
Myth
str.toUpperCase() changes str.
Fact
No string method changes the string. It returns a new one. Even str[0] = "X" silently does nothing (and throws a TypeError in strict mode).
Myth
replace replaces every match.
Fact
With a string pattern, replace changes only the first match. Use replaceAll, or a regex with the g flag (Topic 4.3).
Myth
slice and substring are the same.
Fact
They differ on negatives (substring turns them into 0) and on reversed arguments (substring swaps them, slice returns ""). slice is the predictable one.
Myth
trim() only removes spaces.
Fact
It removes all whitespace and line terminators from both ends: spaces, tabs, \n, \r, the no-break space and others. It never touches whitespace in the middle.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Calling a method on a primitive string works because the engine briefly wraps it in a
Stringobject (auto-boxing), finds the method onString.prototype, and calls it. Engines optimise this away; never createnew String("x")yourself, because it's an object andnew String("a") === new String("a")isfalse. - ▸
Building a big string with
+=in a loop is fast in modern engines: V8 uses ropes (a tree of pieces) and flattens them only when needed. Collecting pieces in an array and callingjoinis also fine; choose whichever reads better. - ▸
slice,substringandatwork in UTF-16 code units, not characters."😀".slice(0, 1)returns half an emoji. Topic 4.2 explains why and how to cut text safely. - ▸
replaceAllwith a regex without thegflag throwsTypeError: replaceAll must be called with a global RegExp.splitwith a limit stops early, so it's cheap on huge strings when you only need the first pieces.
Remember this
- 1
A string is a piece of text, a sequence of characters, each with a position called an index starting at
0(Phase 1, Topic 1.4). A method is a function that belongs to a value, called with a dot:"hello".toUpperCase(). Strings are immutable: no method can change a string in place. Every string method returns a new string (or a number, a boolean or an array), so you must keep the result:name = name.trim(). Writingname.trim();on its own line does nothing useful. - 2
Cutting out a piece.
slice(start, end)returns the characters from indexstartup to but not includingend. Leave outendto go to the finish. Negative numbers count from the end:"report.pdf".slice(-3)is"pdf".substring(start, end)looks the same but has two odd rules: it treats negative numbers as0, and ifstartis bigger thanendit swaps them. Preferslice; it behaves the same way asArray.prototype.slice. (substris a deprecated older method: don't use it in new code.)at(i)(ES2022) reads one character, andat(-1)gives the last one; plainstr[-1]isundefined, because-1isn't an index. - 3
Searching.
indexOf(text)returns the index wheretextfirst appears, or **-1** if it isn't there;lastIndexOfsearches from the end. Both take an optional start position.includes(text),startsWith(text)andendsWith(text)(all ES2015) returntrueorfalse, which reads far better thanindexOf(...) !== -1. All of them are case-sensitive:"Hello".includes("hello")isfalse, so compare lower-cased copies when case shouldn't matter. - 4
Splitting and joining.
split(separator)cuts a string into an array of pieces at every separator:"a,b,c".split(",")gives[ 'a', 'b', 'c' ]. A second argument limits how many pieces you get.split("")splits into single characters (with a Unicode catch you'll meet in Topic 4.2). The opposite is the array methodjoin(separator):["a", "b"].join(" - ")gives"a - b". Split, change the pieces, join again: this is one of the most common moves in JavaScript. - 5
Replacing.
replace(find, newText)replaces only the first match whenfindis a string.replaceAll(find, newText)(ES2021) replaces every match. Watch out: the replacement text treats$specially ($&means "the matched text",$$means a single$), so a price like"$5"in replacement text can surprise you. You can also pass a function as the second argument: it is called for each match and returns the replacement. Topic 4.3 adds regular expressions, which makereplacefar more powerful. - 6
Tidying and shaping.
trim()removes whitespace (spaces, tabs, new lines, and some other blank characters) from both ends;trimStart()andtrimEnd()(ES2019) do one end. Use it on every value typed by a user.padStart(length, fill)andpadEnd(ES2017) add characters until the string reaches a length:"7".padStart(2, "0")gives"07", perfect for clocks and invoice numbers.repeat(n)(ES2015) repeats a stringntimes.toUpperCase()andtoLowerCase()change case, andlocaleComparecompares strings by a language's alphabet (Topic 4.5).
Explain it without notes
Why must you write s = s.trim() instead of s.trim()?
What is the difference between slice and substring?
How do replace and replaceAll differ, and what is special about $ in the replacement?
Why is includes safer than testing indexOf in an if?
Practice
Write a function initials(fullName) that returns the capital initials with dots, so initials(" asha rao ") returns "A.R.".
Write maskCard(number) that shows only the last 4 digits of a 16-digit card number string, like "************3456".
Write ensureHttps(url) that adds https:// to the front if the string doesn't already start with http:// or https://.
Trade-offs
- ↔
Chaining several methods (
trim().toLowerCase().split(...)) reads clearly but creates a new string at each step; on normal text that cost is tiny, while in a hot loop over megabytes a single pass (or a regex) can be faster. - ↔
includes/startsWithare clearer than regular expressions for simple checks; regex is worth it only when the pattern is genuinely a pattern (Topic 4.3).
Done when you can
I can cut pieces of a string with
slice, including with negative indexes.I can search with
indexOf,includes,startsWithandendsWith, and I knowindexOfreturns-1when nothing is found.I can split a string into an array and join an array back into a string.
I know
replacechanges the first match andreplaceAllchanges every match.I can tidy and format text with
trim,padStart,padEndandrepeat.I always keep the return value, because strings never change in place.