Surrogate Pair
Cơ chế trong UTF-16 để biểu diễn ký tự ngoài BMP bằng hai đơn vị mã 16-bit.
Cặp thay thế là cơ chế trong mã hóa UTF-16 để biểu diễn ký tự ngoài Mặt phẳng đa ngôn ngữ cơ bản (BMP: U+0000 đến U+FFFF). Nó kết hợp một surrogate cao (U+D800 đến U+DBFF) và một surrogate thấp (U+DC00 đến U+DFFF) thành hai đơn vị mã 16-bit để biểu diễn một ký tự duy nhất. Khoảng một triệu ký tự trong phạm vi Unicode U+10000 đến U+10FFFF được biểu diễn bằng sơ đồ này.
Hầu hết emoji nằm ngoài BMP và được biểu diễn dưới dạng cặp thay thế. Ví dụ, "😀" (U+1F600) được biểu diễn bởi surrogate cao U+D83D và surrogate thấp U+DE00. String.length của JavaScript trả về số đơn vị mã UTF-16, vì vậy độ dài của "😀" là 2, không phải 1. Tương tự, charAt() và charCodeAt() hoạt động ở cấp đơn vị mã và không thể xử lý chính xác ký tự cặp thay thế.
Để có số ký tự chính xác, sử dụng [...str].length or Array.from(str).length. Các phương pháp này tận dụng giao thức iterator để phân tách chuỗi theo code point, coi cặp thay thế là ký tự đơn. Từ ES2015, codePointAt() and for...of cũng hoạt động ở cấp code point. Tuy nhiên, để đếm chính xác cụm grapheme (ký tự trực quan gồm ký tự kết hợp hoặc chuỗi ZWJ) cần Intl.Segmenter API.
Cặp thay thế chỉ có trong UTF-16 và không tồn tại trong UTF-8 hoặc UTF-32. UTF-8 mã hóa trực tiếp code point bằng chuỗi có độ dài thay đổi (1 đến 4 byte), trong khi UTF-32 sử dụng cố định 4 byte cho tất cả code point. Cột ký tự cơ sở dữ liệu (như sự khác biệt giữa utf8 and utf8mb4của MySQL) cũng có thể gặp vấn đề khi lưu trữ ký tự cặp thay thế.
Từ góc độ đếm ký tự, cặp thay thế đặt ra câu hỏi cơ bản về điều gì tạo thành "một ký tự." Kết quả khác nhau tùy thuộc vào việc bạn đếm đơn vị mã UTF-16, code point hay cụm grapheme. Khi thiết kế công cụ đếm ký tự, điều quan trọng là xác định rõ đơn vị đếm và đảm bảo nó phù hợp với kỳ vọng của người dùng.