JavaScript / charAt()과 charCodeAt()으로 문자와 코드 값 읽기
String.charAt(index)는 지정한 위치의 문자열을, String.charCodeAt(index)는 그 위치의 UTF-16 코드 단위 값을 숫자로 반환합니다. 인덱스는 0부터 시작합니다. 일반적인 영문자 예제에서는 직관적이지만, 일부 이모지처럼 UTF-16 단위 두 개로 표현되는 문자를 다룰 때 두 메서드의 결과가 한 글자 전체를 뜻하지 않을 수 있습니다.
문자와 코드 단위 읽기
charAt()과 charCodeAt()에는 같은 인덱스를 줄 수 있지만 반환값의 종류가 다릅니다. 문자열 "abcde"의 인덱스 3은 네 번째 문자인 d이고, 소문자 d의 UTF-16 코드 단위 값은 100입니다.
const text = 'abcde';
console.log(text.charAt(3)); // 'd'
console.log(text.charCodeAt(3)); // 100
console.log(text.length); // 5
문자열의 숫자 인덱스 접근 text[3]도 보통 같은 위치를 읽지만, 범위를 벗어났을 때의 반환값이 charAt()과 다릅니다. 코드를 숫자로 얻어 비교하거나 변환해야 한다면 charCodeAt()을 사용하세요. MDN의 String.charAt() 문서와 String.charCodeAt() 문서를 참고하세요.
범위를 벗어난 인덱스
문자열 길이 이상의 인덱스를 지정하면 charAt()은 빈 문자열 "", charCodeAt()은 숫자가 아님을 나타내는 NaN을 반환합니다. 음수도 유효한 위치가 아닙니다. 빈 문자열과 NaN은 정상 결과와 구분해서 처리하세요.
const text = 'abcde';
console.log(text.charAt(10)); // ''
console.log(text.charCodeAt(10)); // NaN
console.log(text[10]); // undefined
console.log(Number.isNaN(text.charCodeAt(10))); // true
charCodeAt()의 결과를 숫자로 쓰기 전에 Number.isNaN()으로 확인하거나, 호출 전에 index >= 0 && index < text.length 범위를 확인하세요. 글자 수가 사용자에게 보이는 문자 수와 꼭 같지는 않으므로 이 경계는 UTF-16 인덱스의 경계입니다.
이모지에서는 코드 단위와 코드 포인트 구분하기
JavaScript 문자열의 위치와 length는 UTF-16 코드 단위를 기준으로 합니다. BMP(기본 다국어 평면) 밖의 문자는 서로게이트 쌍인 두 단위로 표현됩니다. 이 위치에서 charAt(0)은 첫 단위만 가져오고, charCodeAt(0)도 첫 단위의 숫자만 반환합니다. 전체 유니코드 코드 포인트를 읽으려면 codePointAt()을 고려하세요.
const face = String.fromCodePoint(0x1F642);
console.log(face.length); // 2
console.log(face.charAt(0).length); // 1: 첫 UTF-16 단위만 가져옴
console.log(face.charCodeAt(0)); // 55357
console.log(face.codePointAt(0)); // 128578
코드 포인트 하나가 항상 사용자에게 보이는 문자 하나인 것도 아닙니다. 결합 문자나 여러 코드 포인트로 이뤄진 이모지는 더 복잡합니다. 사용자 눈에 보이는 글자 단위로 자르거나 세야 한다면 별도의 문자 분할 기준이 필요합니다. MDN의 String.codePointAt() 문서에서 서로게이트 쌍 처리를 확인할 수 있습니다.
전체 예제: 인덱스를 바꿔 결과 비교하기
아래 코드를 UTF-8로 string-character-code.html에 저장해 브라우저에서 엽니다. 문자열과 인덱스를 바꾸면 세 메서드의 결과가 바로 표시됩니다. 출력에는 빈 문자열과 NaN이 그대로 드러나도록 했습니다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>문자 위치와 코드 값 확인</title>
</head>
<body>
<label for="text">문자열</label>
<input id="text" type="text" value="abcde">
<label for="index">UTF-16 인덱스</label>
<input id="index" type="number" min="0" step="1" value="3">
<pre id="output" aria-live="polite"></pre>
<script>
const text = document.querySelector('#text');
const index = document.querySelector('#index');
const output = document.querySelector('#output');
function show() {
const value = text.value;
const position = Number(index.value);
if (index.value.trim() === '' || !Number.isInteger(position) || position < 0) {
output.textContent = '0 이상의 정수 인덱스를 입력하세요.';
return;
}
const unit = value.charCodeAt(position);
const point = value.codePointAt(position);
output.textContent = `문자열 길이(UTF-16 단위): ${value.length}\n` +
`charAt(${position}): ${JSON.stringify(value.charAt(position))}\n` +
`charCodeAt(${position}): ${Number.isNaN(unit) ? 'NaN' : unit}\n` +
`codePointAt(${position}): ${point === undefined ? 'undefined' : point}`;
}
text.addEventListener('input', show);
index.addEventListener('input', show);
show();
</script>
</body>
</html>
초기값 abcde, 인덱스 3에서는 "d", 100, 100이 나옵니다. 인덱스를 10으로 바꾸면 각각 "", NaN, undefined입니다. 이모지를 입력하고 0과 1을 차례로 검사하면 문자열 길이 2와 코드 단위별 차이를 확인할 수 있습니다.




