Unicode & Emoji Encoding Guide: Code Points to Rendering

Unicode: One Encoding Standard for All Characters

Unicode assigns a unique number (code point) to every character in virtually every writing system in the world.

Concept Description Example
Code Point A character's unique number U+4E2D (中)
Plane 65,536 code points per plane BMP (Plane 0)
BMP Basic Multilingual Plane, U+0000~U+FFFF Most common characters
Supplementary planes U+10000~U+10FFFF Emoji, ancient scripts, rare characters

1. The UTF Encoding Family

Encoding Byte Length Encoding of "中" Characteristics
UTF-8 1-4 bytes E4 B8 AD Variable length, ASCII-compatible, the Web standard
UTF-16 2 or 4 bytes 4E2D Variable length, used internally by JS
UTF-32 Fixed 4 bytes 00004E2D Fixed length, high memory usage

UTF-8 Encoding Rules

Code Point Range Bytes Encoding Template
U+0000 ~ U+007F 1 0xxxxxxx
U+0080 ~ U+07FF 2 110xxxxx 10xxxxxx
U+0800 ~ U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx
U+10000 ~ U+10FFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Encoding Example

Character: 中
Code point: U+4E2D = 0100 1110 0010 1101

UTF-8 (3 bytes):
  1110xxxx 10xxxxxx 10xxxxxx
  1110 0100 10 111000 10 101101
  = E4 B8 AD

UTF-16 (2 bytes):
  4E2D

UTF-32 (4 bytes):
  00004E2D

2. JavaScript's Unicode Pitfalls

String Length Is Unreliable

'中'.length              // 1 ✅
'𠮷'.length             // 2 ❌ (supplementary plane character, surrogate pair)
'👨‍👩‍👧‍👦'.length           // 11 ❌ (Emoji ZWJ sequence)
'🏳️‍🌈'.length             // 6 ❌ (flag Emoji)

Why: JS Uses UTF-16

Characters in supplementary planes (code points > U+FFFF) require a surrogate pair (2 UTF-16 code units):

Character: 𠮷 (U+20BB7)
UTF-16 surrogate pair: D842 DF87
JS: length = 2 (two code units)

Correct Approaches

// Method 1: spread operator
[...'𠮷'].length           // 1 ✅
[...'👨‍👩‍👧‍👦'].length         // 1 ✅ (but ZWJ sequences count as one)

// Method 2: Array.from
Array.from('𠮷').length    // 1 ✅

// Method 3: for...of
for (const char of '𠮷👨‍👩‍👧‍👦') {
  console.log(char);  // iterates character by character
}

Working with Code Points

// Get a code point
'中'.codePointAt(0)        // 20013 (0x4E2D)
'𠮷'.codePointAt(0)        // 134047 (0x20BB7)

// Create a character from a code point
String.fromCodePoint(0x4E2D)  // '中'
String.fromCodePoint(0x20BB7) // '𠮷'

// Note: charCodeAt cannot handle supplementary planes
'𠮷'.charCodeAt(0)         // 55362 (only the first surrogate)

3. Emoji Encoding Deep Dive

The Four Types of Emoji

Type Example Code Points Length
Basic Emoji 😀 U+1F600 1 (code point)
Modifier Emoji 👩🏽 U+1F469 + U+1F3FD 2 (code points)
ZWJ sequence 👨‍👩‍👧‍👦 Multiple code points + ZWJ 7 (code points)
Flag Emoji 🇨🇳 U+1F1E8 + U+1F1F3 2 (code points)

Zero Width Joiner (ZWJ)

ZWJ (U+200D) combines multiple emoji into a single composite emoji:

👨 (U+1F468) + ZWJ (U+200D) + 👩 (U+1F469) + ZWJ (U+200D) + 👧 (U+1F467) + ZWJ (U+200D) + 👦 (U+1F466)
= 👨‍👩‍👧‍👦 (family of four)

Skin Tone Modifiers

Skin tone modifiers (U+1F3FB ~ U+1F3FF) change an emoji's skin tone:

👩 (U+1F469) + 🏽 (U+1F3FD) = 👩🏽 (woman with medium skin tone)
Modifier Code Point Skin Tone
🏻 U+1F3FB Light
🏼 U+1F3FC Medium-light
🏽 U+1F3FD Medium
🏾 U+1F3FE Medium-dark
🏿 U+1F3FF Dark

Flag Emoji

Flags are composed of two regional indicator symbols (ISO 3166-1 alpha-2):

🇨 (U+1F1E8) + 🇳 (U+1F1F3) = 🇨🇳 (China)
🇺 (U+1F1FA) + 🇸 (U+1F1F8) = 🇺🇸 (United States)
🇯 (U+1F1EF) + 🇵 (U+1F1F5) = 🇯🇵 (Japan)

4. Variation Selectors

VS16 (U+FE0F) switches a text-presentation character to emoji presentation:

❤ (U+2764)           → text presentation: ❤ (black heart)
❤️ (U+2764 + U+FE0F) → emoji presentation: ❤️ (red heart)
Character Without VS16 With VS16
☕ ☕ (text) ☕️ (emoji)
⚡ ⚡ (text) ⚡️ (emoji)
❤ ❤ (text) ❤️ (emoji)

5. Working with Unicode Using the Toolbox

Unicode Encode/Decode

  1. Open the Unicode Encode/Decode Tool
  2. Enter text or code points
  3. View the conversion results:
    • Text → Unicode escape sequences (\u4E2D)
    • Code points → characters
    • UTF-8/16/32 byte sequences

Emoji Encoding

  1. Open the Emoji Encoding Tool
  2. Enter an emoji
  3. View complete encoding details:
    • Code point sequence
    • UTF-8 bytes
    • ZWJ/modifier breakdown
    • CLDR short name

6. Common Pitfalls and Solutions

Pitfall 1: String Truncation

// Wrong: slicing may cut a surrogate pair in half
text.substring(0, 10)  // may produce invalid characters

// Correct: slice by code point
function sliceByCodePoint(str, start, end) {
  const chars = [...str];
  return chars.slice(start, end).join('');
}

Pitfall 2: String Reversal

// Wrong: reversal breaks composite characters
'👨‍👩‍👧‍👦'.split('').reverse().join('')  // garbled output

// Correct: reverse by code point
[...'👨‍👩‍👧‍👦'].reverse().join('')  // ✅

Pitfall 3: Regex Matching

// Wrong: . does not match supplementary plane characters
/^.$/.test('𠮷')  // false

// Correct: use the u flag
/^.$/u.test('𠮷')  // true

Pitfall 4: Sorting

// Default sorting compares UTF-16 code units
['中', '𠮷', 'a'].sort()  // may not order as expected

// Correct: sort by code point
['中', '𠮷', 'a'].sort((a, b) =>
  a.codePointAt(0) - b.codePointAt(0)
)

7. Useful APIs

// Normalization (handling canonical equivalence of composed characters)
'café'.normalize('NFC')   // café (composed)
'café'.normalize('NFD')   // café (decomposed)

// Detecting emoji
function isEmoji(char) {
  const code = char.codePointAt(0);
  return (code >= 0x1F600 && code <= 0x1F64F) ||  // Emoticons
         (code >= 0x1F300 && code <= 0x1F5FF) ||  // Misc Symbols
         (code >= 0x1F680 && code <= 0x1F6FF) ||  // Transport
         (code >= 0x1F900 && code <= 0x1F9FF);    // Supplemental
}

// Getting the true grapheme length of a string
function graphemeLength(str) {
  const segmenter = new Intl.Segmenter('zh', { granularity: 'grapheme' });
  return [...segmenter.segment(str)].length;
}

graphemeLength('👨‍👩‍👧‍👦')  // 1 ✅
graphemeLength('🏳️‍🌈')    // 1 ✅
#Unicode#Emoji#UTF-8#字符编码#码点