encodeURIComponent가 URIError를 던지는 이유: 짝 잃은 서로게이트
문자열을 slice로 자르다 이모지가 반으로 갈리면 encodeURIComponent는 URIError를 던지고 TextEncoder는 EF BF BD를 씁니다. 짝 잃은 서로게이트의 정체와 isWellFormed·toWellFormed 사용법을 ECMA-262, Unicode 17.0 기준으로 정리했습니다.
사용자 이름을 잘라 URL에 넣는 코드에서 이름 끝에 이모지가 걸리면 이런 일이 생깁니다.
const cut = "ab😀".slice(0, 3); // "ab" + "\uD83D"
encodeURIComponent(cut); // URIError
new TextEncoder().encode(cut); // 61 62 EF BF BD
같은 문자열인데 한쪽은 예외를 던지고, 다른 쪽은 조용히 바이트를 바꿔 내놓습니다. 원인은 잘린 자리에 남은 짝 잃은 서로게이트입니다.
반쪽만 남은 이모지
😀(U+1F600)는 UTF-16에서 D83D DE00 두 코드 유닛으로 나뉩니다(유니코드 표준 표 3-5). slice는 코드 유닛 단위로 자르므로 slice(0, 3)에는 앞쪽 D83D만 남습니다.
유니코드는 D800–DFFF를 서로게이트용으로 떼어 두고 스칼라 값에서 뺍니다(D71–D76). 짝 없이 홀로 있는 서로게이트 코드 유닛은 UTF-16으로서 잘못된 형식입니다(D91). 그래도 JavaScript 문자열은 16비트 값의 나열이라 이런 상태를 그대로 담을 수 있습니다.
UTF-8에는 자리가 없다
RFC 3629 3절은 UTF-8로 U+D800–U+DFFF를 인코딩하는 것을 금지합니다. 그래서 문자열을 UTF-8 바이트로 바꾸는 API는 짝 잃은 서로게이트를 만나면 멈추거나 다른 문자로 바꿔야 합니다.
encodeURI와 encodeURIComponent는 멈춥니다. ECMA-262의 Encode 연산(19.2.6.5)은 코드 포인트를 읽다가 짝 없는 서로게이트가 나오면 URIError를 던집니다. URL 인코딩 글에서 본 decodeURIComponent('%E3%81')의 오류는 바이트가 모자란 경우이고, 이쪽은 인코딩하는 방향에서 나는 오류입니다.
TextEncoder는 바꿉니다. encode()의 인수는 USVString이고, Encoding 표준은 이 변환에서 짝 잃은 서로게이트가 U+FFFD(�)로 바뀐다고 설명합니다. U+FFFD는 UTF-8로 EF BF BD입니다. 원래 이모지는 복구할 수 없게 사라지고, 오류도 나지 않습니다.
미리 확인하고 직접 고르기
ECMA-262에는 이 상태를 다루는 메서드가 두 개 있습니다. isWellFormed()(22.1.3.10)는 짝 잃은 서로게이트가 있는지 알려 주고, toWellFormed()(22.1.3.31)는 그것을 U+FFFD로 바꾼 새 문자열을 돌려줍니다.
cut.isWellFormed(); // false
encodeURIComponent(cut.toWellFormed()); // "ab%EF%BF%BD"
다만 �로 바꾸는 것도 데이터를 잃는 일이라, 애초에 코드 유닛 단위로 자르지 않는 편이 낫습니다. 코드 포인트나 눈에 보이는 글자 단위로 자르는 방법은 글자 수가 도구마다 다른 이유에 정리했습니다. 문자열이 바이트로 나가는 경계에서 isWellFormed()로 확인하면, 예외와 조용한 치환 중 어느 쪽이 일어날지 코드가 정할 수 있습니다.
참고
- ECMAScript 2026, 19.2.6 URI 처리 함수: 19.2.6.3–19.2.6.5
- ECMAScript 2026, 22.1 String 객체: 22.1.3.10, 22.1.3.22, 22.1.3.31
- ECMAScript 2026, 7.2.7 IsStringWellFormedUnicode
- WHATWG Web IDL: 2.13.19 USVString
- WHATWG Encoding Standard: 7.4 TextEncoder, 7.6 TextEncoderStream
- WHATWG Infra Standard: 4.6 Code points, 4.7 Strings
- 유니코드 표준 17.0, 3장: 3.8 Surrogates, 3.9 Unicode Encoding Forms, 표 3-5
- RFC 3629, UTF-8, a transformation format of ISO 10646: 3절
링크와 내용은 2026년 10월 8일에 확인했습니다.