콘텐츠로 건너뛰기
블로그로 돌아가기

"JavaScript에서 이모지를 Unicode로 변환하기"

Leibniz Li

@leibnizli
+

이모지는 단순한 문자가 아니며 많은 이모지가 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); 
// "👨‍👩‍👧‍👦"

활용 사례

  1. 데이터베이스 저장: MySQL 등에서 4바이트 문자를 저장하도록 utf8mb4를 사용하세요.
  2. 입력 제한: 소개나 게시글 길이를 셀 때 [...str].length는 코드 포인트를, str.length는 코드 유닛을 셉니다. ZWJ 조합에는 여전히 여러 코드 포인트가 있으므로 화면상 문자 수와 같지 않습니다.
  3. 사용자 지정 텍스트 렌더러: Canvas 게임과 고성능 UI 구성요소에 유용합니다.

인터랙티브 도구가 필요한가요?

자주 변환한다면 온라인 Unicode 및 이모지 변환기를 사용하세요. 텍스트, 16진수, CSS, JS 이스케이프 시퀀스를 실시간 양방향 변환합니다.

온라인 Unicode 변환기 사용하기 →