Please support this book: buy it or donate

18. Strings



Strings are primitive values in JavaScript and immutable. That is, string-related operations always produce new strings and never change existing strings.

18.1. Plain string literals

Plain string literals are delimited by either single quotes or double quotes:

  1. const str1 = 'abc';
  2. const str2 = "abc";
  3. assert.equal(str1, str2);

Single quotes are used more often, because it makes it easier to mention HTML with its double quotes.

The next chapter covers template literals, which give you:

  • String interpolation
  • Multiple lines
  • Raw string literals (backslash has no special meaning)

18.1.1. Escaping

The backslash lets you create special characters:

  • Unix line break: '\n'
  • Windows line break: '\r\n'
  • Tab: '\t'
  • Backslash: '\'
    The backslash also lets you use the delimiter of a string literal inside that literal:
  1. 'She said: "Let\'s go!"'
  2. "She said: \"Let's go!\""

18.2. Accessing characters and code points

18.2.1. Accessing JavaScript characters

JavaScript has no extra data type for characters – characters are always transported as strings.

  1. const str = 'abc';
  2. // Reading a character at a given index
  3. assert.equal(str[1], 'b');
  4. // Counting the characters in a string:
  5. assert.equal(str.length, 3);

18.2.2. Accessing Unicode code points via for-of and spreading

Iterating over strings via for-of or spreading () visits Unicode code points. Each code point is represented by 1–2 JavaScript characters. For more information, see the section on the atoms of text in this chapter.

This is how you iterate over the code points of a string via for-of:

  1. for (const ch of 'abc') {
  2. console.log(ch);
  3. }
  4. // Output:
  5. // 'a'
  6. // 'b'
  7. // 'c'

And this is how you convert a string into an Array of code points via spreading:

  1. assert.deepEqual([...'abc'], ['a', 'b', 'c']);

18.3. String concatenation via +

If at least one operand is a string, the plus operator (+) converts any non-strings to strings and concatenates the result:

  1. assert.equal(3 + ' times ' + 4, '3 times 4');

The assignment operator += is useful if you want to assemble a string, piece by piece:

  1. let str = ''; // must be `let`!
  2. str += 'Say it';
  3. str += ' one more';
  4. str += ' time';
  5. assert.equal(str, 'Say it one more time');

As an aside, this way of assembling strings is quite efficient, because most JavaScript engines internally optimize it.

18.4. Converting to string

These are three ways of converting a value x to a string:

  • String(x)
  • ''+x
  • x.toString() (does not work for undefined and null)
    Recommendation: use the descriptive and safe String().

Examples:

  1. assert.equal(String(undefined), 'undefined');
  2. assert.equal(String(null), 'null');
  3. assert.equal(String(false), 'false');
  4. assert.equal(String(true), 'true');
  5. assert.equal(String(123.45), '123.45');

Pitfall for booleans: If you convert a boolean to a string via String(), you can’t convert it back via Boolean().

  1. > String(false)
  2. 'false'
  3. > Boolean('false')
  4. true

18.4.1. Stringifying objects

Plain objects have a default representation that is not very useful:

  1. > String({a: 1})
  2. '[object Object]'

Arrays have a better string representation, but it still hides much information:

  1. > String(['a', 'b'])
  2. 'a,b'
  3. > String(['a', ['b']])
  4. 'a,b'
  5. > String([1, 2])
  6. '1,2'
  7. > String(['1', '2'])
  8. '1,2'
  9. > String([true])
  10. 'true'
  11. > String(['true'])
  12. 'true'
  13. > String(true)
  14. 'true'

Stringifying functions returns their source code:

  1. > String(function f() {return 4})
  2. 'function f() {return 4}'

18.4.2. Customizing the stringification of objects

You can override the built-in way of stringifying objects by implementing the method toString():

  1. const obj = {
  2. toString() {
  3. return 'hello';
  4. }
  5. };
  6. assert.equal(String(obj), 'hello');

18.4.3. An alternate way of stringifying values

The JSON data format is a text representation of JavaScript values. Therefore, JSON.stringify() can also be used to stringify data:

  1. > JSON.stringify({a: 1})
  2. '{"a":1}'
  3. > JSON.stringify(['a', ['b']])
  4. '["a",["b"]]'

The caveat is that JSON only supports null, booleans, numbers, strings, Arrays and objects (which it always treats as if they were created by object literals).

Tip: The third parameter lets you switch on multi-line output and specify how much to indent. For example:

  1. console.log(JSON.stringify({first: 'Jane', last: 'Doe'}, null, 2));

This statement produces the following output.

  1. {
  2. "first": "Jane",
  3. "last": "Doe"
  4. }

18.5. Comparing strings

Strings can be compared via the following operators:

  1. < <= > >=

There is one important caveat to consider: These operators compare based on the numeric values of JavaScript characters. That means that the order that JavaScript uses for strings is different from the one used in dictionaries and phone books:

  1. > 'A' < 'B' // ok
  2. true
  3. > 'a' < 'B' // not ok
  4. false
  5. > 'ä' < 'b' // not ok
  6. false

Properly comparing text is beyond the scope of this book. It is supported via the ECMAScript Internationalization API (Intl).

18.6. Atoms of text: JavaScript characters, code points, grapheme clusters

Quick recap of the chapter on Unicode:

  • Code points: Unicode characters, with a range of 21 bits.
  • UTF-16 code units: JavaScript characters, with a range of 16 bits. Code points are encoded as 1–2 UTF-16 code units.
  • Grapheme clusters: Graphemes are written symbols, as displayed on screen or paper. A grapheme cluster is a sequence of 1 or more code points that encodes a grapheme.
    To represent code points in JavaScript strings, one or two JavaScript characters are used. You can see that when counting characters via .length:
  1. // 3 Unicode code points, 3 JavaScript characters:
  2. assert.equal('abc'.length, 3);
  3. // 1 Unicode code point, 2 JavaScript characters:
  4. assert.equal('?'.length, 2);

18.6.1. Working with code points

Let’s explore JavaScript’s tools for working with code points.

Code point escapes let you specify code points hexadecimally. They expand to one or two JavaScript characters.

  1. > '\u{1F642}'
  2. '?'

Converting from code points:

  1. > String.fromCodePoint(0x1F642)
  2. '?'

Converting to code points:

  1. > '?'.codePointAt(0).toString(16)
  2. '1f642'

Iteration honors code points. For example, the iteration-based for-of loop:

  1. const str = '?a';
  2. assert.equal(str.length, 3);
  3. for (const codePoint of str) {
  4. console.log(codePoint);
  5. }
  6. // Output:
  7. // '?'
  8. // 'a'

Or iteration-based spreading ():

  1. > [...'?a']
  2. [ '?', 'a' ]

Spreading is therefore a good tool for counting code points:

  1. > [...'?a'].length
  2. 2
  3. > '?a'.length
  4. 3

18.6.2. Working with code units

Indices and lengths of strings are based on JavaScript characters (i.e., code units).

To specify code units numerically, you can use code unit escapes:

  1. > '\uD83D\uDE42'
  2. '?'

And you can use so-called char codes:

  1. > String.fromCharCode(0xD83D) + String.fromCharCode(0xDE42)
  2. '?'

To get the char code of a character, use .charCodeAt():

  1. > '?'.charCodeAt(0).toString(16)
  2. 'd83d'

18.6.3. Caveat: grapheme clusters

When working with text that may be written in any human language, it’s best to split at the boundaries of grapheme clusters, not at the boundaries of code units.

TC39 is working on Intl.Segmenter, a proposal for the ECMAScript Internationalization API to support Unicode segmentation (along grapheme cluster boundaries, word boundaries, sentence boundaries, etc.).

Until that proposal becomes a standard, you can use one of several libraries that are available (do a web search for “JavaScript grapheme”).

18.7. Quick reference: Strings

Strings are immutable, none of the string methods ever modify their strings.

18.7.1. Converting to string

Tbl. 16 describes how various values are converted to strings.

Table 16: Converting values to strings.
xString(x)
undefined'undefined'
null'null'
Boolean valuefalse 'false', true 'true'
Number valueExample: 123 '123'
String valuex (input, unchanged)
An objectConfigurable via, e.g., toString()

18.7.2. Numeric values of characters and code points

  • Char codes: Unicode UTF-16 code units as numbers
    • String.fromCharCode(), String.prototype.charCodeAt()
    • Precision: 16 bits, unsigned
  • Code points: Unicode code points as numbers
    • String.fromCodePoint(), String.prototype.codePointAt()
    • Precision: 21 bits, unsigned (17 planes, 16 bits each)

18.7.3. String operators

  1. // Access characters via []
  2. const str = 'abc';
  3. assert.equal(str[1], 'b');
  4. // Concatenate strings via +
  5. assert.equal('a' + 'b' + 'c', 'abc');
  6. assert.equal('take ' + 3 + ' oranges', 'take 3 oranges');

18.7.4. String.prototype: finding and matching

  • .endsWith(searchString: string, endPos=this.length): boolean [ES6]

Returns true if the string would end with searchString if its length were endPos. Returns false, otherwise.

  1. > 'foo.txt'.endsWith('.txt')
  2. true
  3. > 'abcde'.endsWith('cd', 4)
  4. true
  • .includes(searchString: string, startPos=0): boolean [ES6]

Returns true if the string contains the searchString and false, otherwise. The search starts at startPos.

  1. > 'abc'.includes('b')
  2. true
  3. > 'abc'.includes('b', 2)
  4. false
  • .indexOf(searchString: string, minIndex=0): number [ES1]

Returns the lowest index at which searchString appears within the string, or -1, otherwise. Any returned index will be minIndex or higher.

  1. > 'abab'.indexOf('a')
  2. 0
  3. > 'abab'.indexOf('a', 1)
  4. 2
  5. > 'abab'.indexOf('c')
  6. -1
  • .lastIndexOf(searchString: string, maxIndex=Infinity): number [ES1]

Returns the highest index at which searchString appears within the string, or -1, otherwise. Any returned index will be maxIndex or lower.

  1. > 'abab'.lastIndexOf('ab', 2)
  2. 2
  3. > 'abab'.lastIndexOf('ab', 1)
  4. 0
  5. > 'abab'.lastIndexOf('ab')
  6. 2
  • .match(regExp: string | RegExp): RegExpMatchArray | null [ES3]

If regExp is a regular expression with flag /g not set, then .match() returns the first match for regExp within the string. Or null if there is no match. If regExp is a string, it is used to create a regular expression before performing the previous steps.

The result has the following type:

  1. interface RegExpMatchArray extends Array<string> {
  2. index: number;
  3. input: string;
  4. groups: undefined | {
  5. [key: string]: string
  6. };
  7. }

Numbered capture groups become Array indices. Named capture groups (ES2018) become properties of .groups. In this mode, .match() works like RegExp.prototype.exec().

Examples:

  1. > 'ababb'.match(/a(b+)/)
  2. { 0: 'ab', 1: 'b', index: 0, input: 'ababb', groups: undefined }
  3. > 'ababb'.match(/a(?<foo>b+)/)
  4. { 0: 'ab', 1: 'b', index: 0, input: 'ababb', groups: { foo: 'b' } }
  5. > 'abab'.match(/x/)
  6. null
  • .match(regExp: RegExp): string[] | null [ES3]

If flag /g of regExp is set, .match() returns either an Array with all matches or null if there was no match.

  1. > 'ababb'.match(/a(b+)/g)
  2. [ 'ab', 'abb' ]
  3. > 'ababb'.match(/a(?<foo>b+)/g)
  4. [ 'ab', 'abb' ]
  5. > 'abab'.match(/x/g)
  6. null
  • .search(regExp: string | RegExp): number [ES3]

Returns the index at which regExp occurs within the string. If regExp is a string, it is used to create a regular expression.

  1. > 'a2b'.search(/[0-9]/)
  2. 1
  3. > 'a2b'.search('[0-9]')
  4. 1
  • .startsWith(searchString: string, startPos=0): boolean [ES6]

Returns true if searchString occurs in the string at index startPos. Returns false, otherwise.

  1. > '.gitignore'.startsWith('.')
  2. true
  3. > 'abcde'.startsWith('bc', 1)
  4. true

18.7.5. String.prototype: extracting

  • .slice(start=0, end=this.length): string [ES3]

Returns the substring of the string that starts at (including) index start and ends at (excluding) index end. You can use negative indices where -1 means this.length-1 (etc.).

  1. > 'abc'.slice(1, 3)
  2. 'bc'
  3. > 'abc'.slice(1)
  4. 'bc'
  5. > 'abc'.slice(-2)
  6. 'bc'
  • .split(separator: string | RegExp, limit?: number): string[] [ES3]

Splits the string into an Array of substrings – the strings that occur between the separators. The separator can either be a string or a regular expression. Captures made by groups in the regular expression are included in the result.

  1. > 'abc'.split('')
  2. [ 'a', 'b', 'c' ]
  3. > 'a | b | c'.split('|')
  4. [ 'a ', ' b ', ' c' ]
  5. > 'a : b : c'.split(/ *: */)
  6. [ 'a', 'b', 'c' ]
  7. > 'a : b : c'.split(/( *):( *)/)
  8. [ 'a', ' ', ' ', 'b', ' ', ' ', 'c' ]
  • .substring(start: number, end=this.length): string [ES1]

Use .slice() instead of this method. .substring() wasn’t implemented consistently in older engines and doesn’t support negative indices.

18.7.6. String.prototype: combining

  • .concat(…strings: string[]): string [ES3]

Returns the concatenation of the string and strings. 'a'+'b' is equivalent to 'a'.concat('b') and more concise.

  1. > 'ab'.concat('cd', 'ef', 'gh')
  2. 'abcdefgh'
  • .padEnd(len: number, fillString=' '): string [ES2017]

Appends fillString to the string until it has the desired length len.

  1. > '#'.padEnd(2)
  2. '# '
  3. > 'abc'.padEnd(2)
  4. 'abc'
  5. > '#'.padEnd(5, 'abc')
  6. '#abca'
  • .padStart(len: number, fillString=' '): string [ES2017]

Prepends fillString to the string until it has the desired length len.

  1. > '#'.padStart(2)
  2. ' #'
  3. > 'abc'.padStart(2)
  4. 'abc'
  5. > '#'.padStart(5, 'abc')
  6. 'abca#'
  • .repeat(count=0): string [ES6]

Returns a string that is the string, repeated count times.

  1. > '*'.repeat()
  2. ''
  3. > '*'.repeat(3)
  4. '***'

18.7.7. String.prototype: transforming

  • .normalize(form: 'NFC'|'NFD'|'NFKC'|'NFKD' = 'NFC'): string [ES6]

Normalizes the string according to the Unicode Normalization Forms.

  • .replace(searchValue: string | RegExp, replaceValue: string): string [ES3]

Replace matches of searchValue with replaceValue. If searchValue is a string, only the first verbatim occurrence is replaced. If searchValue is a regular expression without flag /g, only the first match is replaced. If searchValue is a regular expression with /g then all matches are replaced.

  1. > 'x.x.'.replace('.', '#')
  2. 'x#x.'
  3. > 'x.x.'.replace(/./, '#')
  4. '#.x.'
  5. > 'x.x.'.replace(/./g, '#')
  6. '####'

Special characters in replaceValue are:

  • $$: becomes $
  • $n: becomes the capture of numbered group n (alas, $0 does not work)
  • $&: becomes the complete match
  • $`: becomes everything before the match
  • $': becomes everything after the match
    Examples:
  1. > 'a 2020-04 b'.replace(/([0-9]{4})-([0-9]{2})/, '|$2|')
  2. 'a |04| b'
  3. > 'a 2020-04 b'.replace(/([0-9]{4})-([0-9]{2})/, '|$&|')
  4. 'a |2020-04| b'
  5. > 'a 2020-04 b'.replace(/([0-9]{4})-([0-9]{2})/, '|$`|')
  6. 'a |a | b'

Named capture groups (ES2018) are supported, too:

  • $<name> becomes the capture of named group name
    Example:
  1. > 'a 2020-04 b'.replace(/(?<year>[0-9]{4})-(?<month>[0-9]{2})/, '|$<month>|')
  2. 'a |04| b'
  • .replace(searchValue: string | RegExp, replacer: (…args: any[]) => string): string [ES3]

If the second parameter is a function occurrences are replaced with the strings it returns. Its parameters args are:

  • matched: string: the complete match
  • g1: string|undefined: the capture of numbered group 1
  • g2: string|undefined: the capture of numbered group 2
  • (Etc.)
  • offset: number: where was the match found in the input string?
  • input: string: the whole input string
  1. const regexp = /([0-9]{4})-([0-9]{2})/;
  2. const replacer = (all, year, month) => '|' + all + '|';
  3. assert.equal(
  4. 'a 2020-04 b'.replace(regexp, replacer),
  5. 'a |2020-04| b');

Named capture groups (ES2018) are supported, too. If there are any, a last parameter contains an object whose properties contain the captures:

  1. const regexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})/;
  2. const replacer = (...args) => {
  3. const groups=args.pop();
  4. return '|' + groups.month + '|';
  5. };
  6. assert.equal(
  7. 'a 2020-04 b'.replace(regexp, replacer),
  8. 'a |04| b');
  • .toUpperCase(): string [ES1]

Returns a copy of the string in which all lowercase alphabetic characters are converted to uppercase. How well that works for various alphabets depends on the JavaScript engine.

  1. > '-a2b-'.toUpperCase()
  2. '-A2B-'
  3. > 'αβγ'.toUpperCase()
  4. 'ΑΒΓ'
  • .toLowerCase(): string [ES1]

Returns a copy of the string in which all uppercase alphabetic characters are converted to lowercase. How well that works for various alphabets depends on the JavaScript engine.

  1. > '-A2B-'.toLowerCase()
  2. '-a2b-'
  3. > 'ΑΒΓ'.toLowerCase()
  4. 'αβγ'
  • .trim(): string [ES5]

Returns a copy of the string in which all leading and trailing whitespace (spaces, tabs, line terminators, etc.) is gone.

  1. > '\r\n#\t '.trim()
  2. '#'
  • .trimEnd(): string [ES2019]

Similar to .trim(), but only the end of the string is trimmed:

  1. > ' abc '.trimEnd()
  2. ' abc'
  • .trimStart(): string [ES2019]

Similar to .trim(), but only the beginning of the string is trimmed:

  1. > ' abc '.trimStart()
  2. 'abc '

18.7.8. String.prototype: chars, char codes, code points

  • .charAt(pos: number): string [ES1]

Returns the character at index pos, as a string (JavaScript does not have a datatype for characters). str[i] is equivalent to str.charAt(i) and more concise (caveat: may not work on old engines).

  1. > 'abc'.charAt(1)
  2. 'b'
  • .charCodeAt(pos: number): number [ES1]

Returns the 16-bit number (0–65535) of the UTF-16 code unit (character) at index pos.

  1. > 'abc'.charCodeAt(1)
  2. 98
  • .codePointAt(pos: number): number | undefined [ES6]

Returns the 21-bit number of the Unicode code point of the 1–2 characters at index pos. If there is no such index, it returns undefined.

18.7.9. Sources