2 Strings
This chapter explains that strings in JavaScript are simple in concept but surprisingly subtle in practice. It starts from a real bug caused by assuming all file names used the same encoding, showing how text can be misread when different systems disagree about how bytes map to characters. From there, it lays out the idea that JavaScript strings are defined by the language as sequences of 16-bit values, which means the underlying representation is not the same thing as human-readable text and can even include raw byte-like data in some cases.
The chapter then builds the encoding foundation needed to understand modern text handling. It compares ASCII with Unicode and walks through UTF-32, UTF-16, and UTF-8, emphasizing the trade-offs between simplicity, memory use, and compatibility. It also shows how Unicode normalization matters because visually identical text can be represented by different code unit sequences, which affects equality checks, lengths, user registration, and security. Related topics such as regular expressions and string boundaries are also tied to encoding behavior, especially the need to use Unicode-aware matching when working beyond basic ASCII.
Finally, the chapter connects these language rules to the way JavaScript engines actually work, especially V8. It describes several internal string forms such as one-byte and two-byte strings, concatenated strings, sliced strings, thin strings, externalized strings, and internalized strings, explaining how these representations help reduce copying and improve performance. The main lesson is that naïve string building, repeated concatenation, and careless caching can create major memory and speed problems, while patterns like accumulating fragments and joining them later let the engine optimize effectively. The chapter closes by stressing that although developers usually should think in terms of abstract strings, understanding implementation details can be essential for correctness, performance, and avoiding tricky bugs.
The ASCII mapping of numbers to symbols. Source: https://en.m.wikipedia.org/wiki/File:ASCII-Table-wide.svg
An inefficient way of combining two strings would be to allocate a whole new memory space that is the combined length of the originals, then copying each of the originals into the new space in sequence. The total allocated memory is exactly twice that of the original strings.
V8’s internal ConsString structure instead models the combined string as nothing more than a pointer to each of the original strings. The total allocated memory is the original strings plus the cost of the two pointers.
Flattening a string causes V8 to walk the structure of a tree of ConsStrings, ThinStrings, and SlicedStrings and copy their contents into a new sequential buffer, fully replacing each with the sequential copy. This inreases memory use but makes code that needs to access the contents of the string much faster.
Summary
- JavaScript strings are sequences of 16-bit unsigned integers that may or may not represent valid UTF-16 text
- Character encodings map symbols to numbers, with ASCII handling 128 characters and Unicode supporting over 155,000
- UTF-8 uses 1-4 bytes per character for efficient transmission while UTF-16 uses 2-4 bytes for JavaScript's internal representation
- Surrogate pairs combine two 16-bit values to represent Unicode characters beyond 65,535
- ByteString holds uninterpreted bytes, DOMString allows invalid UTF-16, and USVString guarantees valid Unicode sequences
- File system encoding mismatches cause real bugs when systems assume UTF-8 but encounter legacy encodings like Shift-JIS
- String immutability prevents in-place modification, enabling V8's ConsString optimization for concatenation
- Unicode normalization with normalize() resolves visual equivalence issues when comparing strings with combining characters
- Regular expressions require the 'u' flag to correctly handle surrogate pairs and Unicode patterns
- V8 implements seven string types internally: OneByteString, TwoByteString, ConsString, SlicedString, ExternalString, Internalized and ThinString
- ConsStrings avoid copying during concatenation by storing references, reducing memory usage from quadratic to linear
- String operations have platform-specific memory limits, typically 256-512MB maximum length
JavaScript in Depth ebook for free