"JavaScript에서 이모지를 Unicode로 변환하기"
이모지는 단순한 문자가 아니며 많은 이모지가 Unicode 보충 평면에 있습니다. JavaScript의 처리 방식을 이해하는 것은 문자열 길이, 데이터베이스 및 크로스플랫폼 표시에 중요합니다.
문제: .length가 오해를 주는 이유
JavaScript 문자열은 UTF-16입니다. A나 ☃ 같은 문자는 **기본 다국어 평면(BMP)**에 있고 16비트 코드 유닛 하나를 차지합니다. 😀 같은 이모지는 보충 평면에 있어 16비트 코드 유닛 두 개의 서로게이트 쌍으로 표현됩니다.
'A'.length; // 1 (BMP)
'☃'.length; // 1 (BMP)
'😀'.length; // 2 (Supplementary Plane - Surrogate Pair)
'👨👩👧👦'.length; // 11 (Wait, what? See "ZWJ" below)
방법 1: String.prototype.codePointAt()
ES6 이전의 charCodeAt()는 서로게이트 쌍의 앞부분만 반환했습니다. 현대 JavaScript의 codePointAt()는 전체 Unicode 코드 포인트를 올바르게 가져옵니다.
const emoji = '😀';
// ❌ The old way (Incorrect for emojis)
console.log(emoji.charCodeAt(0)); // 55357 (Surrogate lead)
// ✅ The modern way
console.log(emoji.codePointAt(0)); // 128512
console.log(emoji.codePointAt(0).toString(16)); // "1f600"
방법 2: String.fromCodePoint()
Unicode 숫자를 보이는 이모지로 되돌리려면 정적 메서드 String.fromCodePoint()를 사용하세요.
// Decimal
String.fromCodePoint(128512); // "😀"
// Hexadecimal
String.fromCodePoint(0x1F600); // "😀"
// Multiple points
String.fromCodePoint(0x1F1FA, 0x1F1F8); // "🇺🇸"
여러 문자 일괄 변환
여러 이모지와 문자가 있는 문자열도 한 번에 변환할 수 있습니다. **전개 연산자 [...]**는 Unicode 코드 포인트 단위로 순회하므로 서로게이트 쌍을 쪼개지 않습니다.
1. 문자열을 숫자로 변환(인코딩)
.map()으로 10진수 또는 16진수 Unicode 배열을 얻으세요.
const text = "Hi 😀 🚀";
// Convert to Decimal numbers
const decimals = [...text].map(char => char.codePointAt(0));
console.log(decimals);
// [72, 105, 32, 128512, 32, 128640]
// Convert to Hexadecimal strings (common for CSS/JS escape)
const hexCodes = [...text].map(char => `0x${char.codePointAt(0).toString(16)}`);
console.log(hexCodes);
// ["0x48", "0x69", "0x20", "0x1f600", "0x20", "0x1f680"]
2. 숫자를 문자열로 변환(디코딩)
읽을 수 있는 문자열로 되돌리려면 String.fromCodePoint와 **전개 구문 ...**를 사용하세요.
const myNumbers = [128512, 128640, 9731];
// This "spreads" the array items as individual arguments
const result = String.fromCodePoint(...myNumbers);
console.log(result); // "😀🚀☃"
3. 간단한 반복문 사용
.map()이 낯설다면 for...of 반복문도 잘 작동합니다.
const input = "🍎🍊";
for (let char of input) {
let hex = char.codePointAt(0).toString(16);
console.log(`Character: ${char} -> Unicode: U+${hex.toUpperCase()}`);
}
// Character: 🍎 -> Unicode: U+1F34E
// Character: 🍊 -> Unicode: U+1F34A
복잡한 이모지: 폭 없는 결합자(ZWJ)
일부 이모지는 한 문자처럼 보이지만 여러 Unicode 코드 포인트가 **폭 없는 결합자(ZWJ, \u200D)**로 연결된 시퀀스입니다.
가족 이모지 👨👩👧👦는 다음으로 구성됩니다.
남자 + ZWJ + 여자 + ZWJ + 소녀 + ZWJ + 소년.
전개 연산자 [...]나 Array.from()으로 코드 포인트를 올바르게 순회할 수 있지만 전체 가족 시퀀스를 하나의 표시 문자로 취급하지는 않습니다.
const complexEmoji = '👨👩👧👦';
// ❌ Standard split (breaks the emoji)
console.log(complexEmoji.split(''));
// ["\ud83d", "\udc68", "", "\ud83d", ...]
// ✅ Unicode-aware iteration
const points = [...complexEmoji];
console.log(points);
// ["👨", "", "👩", "", "👧", "", "👦"]
// 1. Get code points as Hexadecimal (commonly used in docs)
const hexCodes = [...complexEmoji].map(c => `0x${c.codePointAt(0).toString(16)}`);
console.log(hexCodes);
// ["0x1f468", "0x200d", "0x1f469", "0x200d", "0x1f467", "0x200d", "0x1f466"]
// 2. Get code points as Decimal (Non-Hexadecimal)
const decimalCodes = [...complexEmoji].map(c => c.codePointAt(0));
console.log(decimalCodes);
// [128104, 8205, 128105, 8205, 128103, 8205, 128102]
// 3. Convert Decimal back to Emoji
console.log(String.fromCodePoint(...decimalCodes));
// "👨👩👧👦"
// 4. Convert Hex strings back to Emoji
const emojiFromHex = String.fromCodePoint(...hexCodes);
console.log(emojiFromHex);
// "👨👩👧👦"
활용 사례
- 데이터베이스 저장: MySQL 등에서 4바이트 문자를 저장하도록
utf8mb4를 사용하세요. - 입력 제한: 소개나 게시글 길이를 셀 때
[...str].length는 코드 포인트를,str.length는 코드 유닛을 셉니다. ZWJ 조합에는 여전히 여러 코드 포인트가 있으므로 화면상 문자 수와 같지 않습니다. - 사용자 지정 텍스트 렌더러: Canvas 게임과 고성능 UI 구성요소에 유용합니다.
인터랙티브 도구가 필요한가요?
자주 변환한다면 온라인 Unicode 및 이모지 변환기를 사용하세요. 텍스트, 16진수, CSS, JS 이스케이프 시퀀스를 실시간 양방향 변환합니다.