String.prototype.codePointAt(index)
codePointAt returns the full Unicode code point starting at the
given position. Where charCodeAt
returns a single 16-bit unit, codePointAt recombines surrogate
pairs into one value between 0 and 0x10FFFF.
Basic usage
const s = "a\uD83D\uDE00b"; // "a😀b"
s.codePointAt(0); // 97 ('a')
s.codePointAt(1); // 128512 (😀, one value, not two)
s.codePointAt(2); // 56832 (low surrogate! see below)
s.codePointAt(3); // 98 ('b')
Indexing is still by UTF-16 units
The index argument counts UTF-16 positions, not characters.
Calling codePointAt(2) in the example above lands on the
second half of the surrogate pair, and the method dutifully returns
that lone low surrogate (56832). It does not skip ahead for you.
To walk a string by code points, test the position or use the iterator:
const s = "a\uD83D\uDE00b";
let cps = [];
for (let i = 0; i < s.length; i++) {
const cp = s.codePointAt(i);
cps.push(cp);
if (cp > 0xFFFF) i++; // skip low surrogate
}
cps; // [97, 128512, 98]
[...s].map(c => c.codePointAt(0)); // same result, simpler
charCodeAt vs codePointAt
| Expression (s = "😀") | Result |
|---|---|
s.charCodeAt(0) | 55357 (high surrogate) |
s.charCodeAt(1) | 56832 (low surrogate) |
s.codePointAt(0) | 128512 (the emoji) |
s.codePointAt(1) | 56832 (lone low surrogate) |
Out-of-range behavior
Like charCodeAt, an out-of-range index returns
undefined — wait, no: it returns undefined for
codePointAt (while charCodeAt returns
NaN). This asymmetry is worth remembering when validating
input:
"ab".charCodeAt(9); // NaN
"ab".codePointAt(9); // undefined
See also
charCodeAt— 16-bit view.fromCodePoint— numbers back to strings.