ASCII in JavaScript
charCodeAt, codePointAt, and Text Encoding
JavaScript strings are UTF-16 internally, which means every character occupies at least 2 bytes in memory regardless of whether it falls in the ASCII range. The charCodeAt() method returns the UTF-16 code unit at a given index, and for ASCII characters (codes 0 through 127) that value matches the ASCII table exactly. Beyond 127, charCodeAt() returns UTF-16 values, and surrogate pairs enter the picture. codePointAt(), added in ES2015, handles the full Unicode range correctly.
1. charCodeAt() and String.fromCharCode()
charCodeAt(index) returns the UTF-16 code unit (an integer 0 through 65535) at the given string position. For all 128 ASCII characters, the returned value matches the decimal column in the ASCII table.
'A'.charCodeAt(0) // 65
'a'.charCodeAt(0) // 97
'0'.charCodeAt(0) // 48
'\n'.charCodeAt(0) // 10
'Hello'.charCodeAt(4) // 111 (o)
String.fromCharCode() reverses the operation. Pass one or more integers and it returns the corresponding string.
String.fromCharCode(65) // 'A'
String.fromCharCode(72,101,108) // 'Hel'
String.fromCharCode(0x41) // 'A' (hex literal)
The value 65 for A and 48 for digit 0 are the same in ASCII, Unicode, and UTF-16 because Unicode preserves the original ASCII mapping for its first 128 code points. Verify any character's value with the text to ASCII converter.
2. codePointAt() and String.fromCodePoint()
ES2015 added codePointAt() to handle characters outside the Basic Multilingual Plane (BMP) - those with code points above 65535. These characters require two UTF-16 code units (a surrogate pair), and charCodeAt() returns each surrogate separately instead of the actual code point.
// Emoji: code point U+1F600
const grin = '\u{1F600}';
grin.charCodeAt(0) // 55357 (high surrogate)
grin.codePointAt(0) // 128512 (correct code point)
// ASCII characters: both methods agree
'Z'.charCodeAt(0) // 90
'Z'.codePointAt(0) // 90
For pure ASCII work, charCodeAt() is sufficient and slightly faster. Switch to codePointAt() when processing user input that might contain emoji, CJK characters, or mathematical symbols. See the Unicode glossary entry for why these distinctions matter.
3. ASCII Validation
No built-in isASCII() exists in JavaScript. Write your own:
function isASCII(str) {
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 127) return false;
}
return true;
}
// Or using a regex
const isASCII = (str) => /^[\x00-\x7F]*$/.test(str);
The regex version is more concise but slightly slower on long strings because the regex engine must compile the pattern. Both correctly identify all 128 ASCII values (0 through 127), including control characters.
To check for printable ASCII only (positions 32 through 126, the 95 printable characters), adjust the range:
const isPrintableASCII = (str) => /^[\x20-\x7E]*$/.test(str);
4. TextEncoder and TextDecoder
TextEncoder converts strings to Uint8Array byte sequences. It always encodes as UTF-8, and since UTF-8 is backward compatible with ASCII, pure ASCII strings produce one byte per character.
const encoder = new TextEncoder();
const bytes = encoder.encode('Hello');
// Uint8Array [72, 101, 108, 108, 111]
const decoder = new TextDecoder('ascii');
decoder.decode(bytes); // 'Hello'
TextDecoder accepts encoding labels like 'ascii', 'utf-8', 'iso-8859-1', and 'windows-1252'. Setting fatal: true changes error handling:
// Strict mode: throw on invalid bytes
const strict = new TextDecoder('ascii', { fatal: true });
strict.decode(new Uint8Array([200])); // TypeError: invalid byte
// Default: replace with U+FFFD
const lenient = new TextDecoder('ascii');
lenient.decode(new Uint8Array([200])); // '\ufffd'
The encoding comparison tool shows how the same byte value maps to different characters across ASCII, ISO 8859-1, and Windows-1252.
5. ArrayBuffer and Typed Arrays
Binary protocols, WebSocket messages, and file readers produce ArrayBuffer objects. Wrap them in a Uint8Array to access individual bytes, then decode ASCII portions:
// Reading ASCII from an ArrayBuffer
const buffer = new ArrayBuffer(5);
const view = new Uint8Array(buffer);
view.set([72, 101, 108, 108, 111]); // 'Hello' in ASCII
const text = new TextDecoder('ascii').decode(view);
console.log(text); // 'Hello'
For WebSocket binary frames, parse ASCII headers by slicing the buffer at known offsets. Many binary protocols (SMTP, HTTP/1.1, FTP) use ASCII text for headers and commands, switching to binary only for payload data. The control characters article covers the CR/LF line endings these protocols use.
6. String Comparison by Code Point
JavaScript's <, >, and localeCompare() compare strings by UTF-16 code units. For ASCII strings, this produces the same ordering as sorting by ASCII value: digits (48-57) before uppercase (65-90) before lowercase (97-122).
'A' < 'a' // true (65 < 97)
'Z' < 'a' // true (90 < 97)
'9' < 'A' // true (57 < 65)
'abc'.localeCompare('ABC') // varies by locale
The default sort order matches the ASCII table layout. localeCompare() overrides this with locale-aware rules (case-insensitive, accent folding). For strict ASCII-order sorting, use .sort((a, b) => a < b ? -1 : a > b ? 1 : 0).
7. Building an ASCII Table in JavaScript
for (let i = 0; i < 128; i++) {
const char = (i >= 32 && i <= 126)
? String.fromCharCode(i)
: `(ctrl-${i})`;
const hex = i.toString(16).toUpperCase().padStart(2, '0');
const bin = i.toString(2).padStart(7, '0');
console.log(`${i.toString().padStart(3)} 0x${hex} ${bin} ${char}`);
}
This produces the same output as the ASCII chart view. Use the base converter for interactive decimal-hex-binary conversions without writing code.
8. Practical Recipes
Strip non-ASCII characters
const stripNonASCII = (str) =>
str.replace(/[^\x00-\x7F]/g, '');
Convert string to ASCII code array
const toASCIICodes = (str) =>
Array.from(str, c => c.charCodeAt(0));
toASCIICodes('Hi!'); // [72, 105, 33]
Caesar cipher (rotate by N positions)
function caesarShift(text, shift) {
return Array.from(text, c => {
const code = c.charCodeAt(0);
if (code >= 65 && code <= 90) // A-Z
return String.fromCharCode(((code - 65 + shift) % 26) + 65);
if (code >= 97 && code <= 122) // a-z
return String.fromCharCode(((code - 97 + shift) % 26) + 97);
return c;
}).join('');
}
caesarShift('Hello', 13); // 'Uryyb' (ROT13)
ROT13 works because the 26 ASCII letters split evenly: rotating by 13 positions and applying again recovers the original text. The ASCII to text tool can verify the code values at each step.
Detect encoding issues in API responses
async function fetchAndValidate(url) {
const res = await fetch(url);
const buffer = await res.arrayBuffer();
const bytes = new Uint8Array(buffer);
const hasNonASCII = bytes.some(b => b > 127);
if (hasNonASCII) {
console.warn('Response contains non-ASCII bytes');
}
return new TextDecoder('utf-8').decode(bytes);
}