문자 인코딩
컴퓨터는 오직 0과 1만 인식한다. 사람이 사용하는 언어나 문자를 이해할 수 없으므로 컴퓨터에 문자를 표현하기 위해선 문자를 0과 1로 변환하는 과정이 필요하다. 각 글자마다 숫자를 대응하고 이를 컴퓨터가 이해하는 0과 1의 이진법으로 변환하는 위 작업을 인코딩(Encoding)이라고 한다.
문자열 세트(Character Set)
인코딩은 약속에 의해 이뤄진다. 예를 들어 ‘A는 65, B는 66, C는 67에 대응되도록 한다.’ 와 같이 글자 별로 대응할 숫자를 정한 뒤 컴퓨터는 이 규칙을 따라 문자를 화면에 처리한다. 이처럼 컴퓨터에서 처리할 글자들을 정하고 각 글자마다 대응하는 숫자를 지정해 체계화 시킨 방식을 문자열 세트(Character Set)라고 한다. 어떠한 문자열 세트를 사용하냐에 따라 컴퓨터가 처리할 수 있는 문자가 달라지고 해당 문자가 대응되는 숫자도 달라진다.
HTML의 <head> 태그를 보면 아래와 같이 meta 태그의 charset=”UTF-8” 속성을 볼 수 있는데, 해당 HTML을 인코딩할 때 사용할 문자열 세트를 지정하는 부분이다.
<html>
<head>
<meta charaset="UTF-8">
<title></title>
</head>
</html>
유니코드(Unicode)
초창기 컴퓨터는 알파벳, 숫자 그리고 몇 개의 특수문자만 숫자로 매칭하는 아스키 코드(ASCII) 문자열 세트를 적용하였다. 그러나 컴퓨터를 사용하는 국가가 점점 늘어나고, 아스키 코드로는 영어 이외의 다른 문자를 컴퓨터에 표현할 수 없자 언어 별 다양한 문자열 세트가 등장하였다.(한글 또한 예전에는 주로 EUC-KR 방식을 사용해 인코딩을 했다.)
문자마다 각기 다른 문자열 세트를 사용하다보니 서로 다른 언어 간 글자가 화면에서 깨져보이는 일이 발생했고, 이를 통합하고자 전 세계 언어를 대응할 수 있는 문자열 세트를 만들었는데 이것이 바로 유니코드(Unicode)다.
유니코드는 다양한 인코딩 방식을 가지고 있다. 즉 문자를 이진수로 저장할 때 몇 개의 자리수를 사용하느냐에 따라 UTF-8, UTF-16, UTF-32 등의 방식으로 구분된다. UTF-8 인코딩 방식을 가장 흔하게 사용하며 javascript는 UTF-16 방식을 사용한다.
javascript에서 문자열 길이
MDN에서는 String.length 를 아래와 같이 설명한다.
length 속성은 UTF-16 코드 유닛을 기준으로 문자열의 길이를 나타냅니다.
코드 유닛(Code Unit)이란 문자열 인코딩 방식에서 하나의 문자를 구성하는 가장 최소 비트 단위를 의미한다. UTF-16 인코딩 방식은 하나의 문자에 16비트의 이진수를 최소 단위로 사용하여 표현한다. 즉 String.length값이 1인 인 문자는 UTF-16 인코딩 방식으로 16비트의 길이를, String.length값이 2인 문자는 32비트의 길이를 가지고 있다는 의미이다.
Surrogate Pair
UTF-16 인코딩 방식은 기본적으로 16비트 내에서 문자를 표현하지만, 전 세계의 모든 문자를 16비트만으로 표현하기엔 역부족이었다. 따라서 기본 코드유닛인 16비트에 16비트 한 단위를 추가하여 32비트로 문자를 표현하는 방식이 사용되었는데, 2개의 코드 유닛을 사용하여 문자를 표현하는 방식을 Surrogate Pair (써로게이트 페어)라고 한다. 위에서 살펴본 🥰와 같은 emoji는 써로게이트 페어 방식으로 인코딩되는 가장 대표적인 문자이다.
Surrogate Pair 방식 외에 특정 조합형 문자들 또한 2개 이상의 코드 유닛으로 표현되기도 한다. 위에서 살펴본 태국어 ค์가 그 예이다.
Grapheme
그럼 코드 유닛 단위가 아닌 사람이 인지하는 문자 그대로의 길이는 어떻게 알 수 있을까?
사람이 인지하는 문자 단위를 Grapheme(문자소)라고 한다. javascript의 Intl.Segmenter 생성자를 이용하면 지정한 locale에 맞춰 grapheme 단위로 문자를 추출해 길이를 얻을 수 있다.
const text = '🏳️⚧️🏳️🌈👩🏾❤️👨🏻';
console.log(text.length);
const segmentLength = Array.from(new Intl.Segmenter().segment(text)).length;
console.log(segmentLength);
locale을 태국어로 하는 경우
const text = 'ค์';
const segmenterTh = new Intl.Segmenter('th', { granularity: 'grapheme' });
const segmentLength = Array.from(segmenterTh.segment(text)).length;
console.log(segmentLength);
Int.Segmenter에 대한 자세한 설명은 MDN에서 확인할 수 있다.