'π'.length === 2? μλ°μ€ν¬λ¦½νΈμμ μ΄λͺ¨μ§ λ¬Έμμ΄ κΈΈμ΄μ ν¨μ κ³Ό ν΄κ²°λ²
2025-10-08νλ‘ νΈμλμμ λ¬Έμμ΄ κΈΈμ΄λ₯Ό κ²μ¬νλ€ λ³΄λ©΄ μ΄λ° κ²½νμ΄ μμ κ±°μμ.
"μ΄λͺ¨μ§ νλλ₯Ό λ£μλλ° κΈΈμ΄κ° 2λ‘ κ³μ°λΌ?"
'π'.length // 2
μ΄κ±΄ μλ°μ€ν¬λ¦½νΈμ λ¬Έμμ΄ λͺ¨λΈμ΄ μ λμ½λμ λ§λ¬Όλ¦¬λ μ§μ μμ μκΈ°λ, μμ£Ό ννμ§λ§ ν·κ°λ¦¬λ λ¬Έμ μμ.
μ 'π'.lengthλ 2μΌκΉ?
μλ°μ€ν¬λ¦½νΈ λ¬Έμμ΄μ UTF-16 μ½λ μ λ λ¨μλ‘ μ μ₯λΌμ. μ¦, String.prototype.lengthλ μ€μ "보μ΄λ κΈμ μ"κ° μλλΌ "16λΉνΈ μ‘°κ° κ°μ"λ₯Ό λ°ννλ κ±°μ£ .
"π" (U+1F344)μ BMP(κΈ°λ³Έ λ€κ΅μ΄ νλ©΄) λ°μ λ¬ΈμλΌ 16λΉνΈ λ μ‘°κ°(μλ‘κ²μ΄νΈ νμ΄)λ‘ ννλΌμ. BMPλ U+0000λΆν° U+FFFFκΉμ§μ λ²μμΈλ°, μ΄ μμ λλ λ¬Έμλ μ½λ μ λ νλλ‘ ννλμ§λ§ κ·Έ λ°μ λ¬Έμλ λ κ°μ μ½λ μ λ, μ¦ μλ‘κ²μ΄νΈ νμ΄λ‘ ννν΄μΌ νκ±°λ μ. μ΄λͺ¨μ§ λλΆλΆμ΄ μ΄ λ²μ λ°μ μμ΄μ .lengthκ° 2κ° λλ κ±°μμ.
"π".length; // 2
"π".charCodeAt(0).toString(16); // "d83c"
"π".charCodeAt(1).toString(16); // "df44"
"π".codePointAt(0).toString(16); // "1f344"
κ²°κ΅ 'π'μ μ½λ μ λ 2κ°λ‘ ꡬμ±λλκΉ .length === 2κ° λλ κ±°μμ.
"κΈΈμ΄"λ 무μμ μΈλλμ λ°λΌ λ¬λΌμ§λ€
μλ°μ€ν¬λ¦½νΈμμ λ¬Έμμ΄μ "κΈΈμ΄"λ μκ°λ³΄λ€ μ¬λ¬ μΈ΅μλ‘ λλμ΄μ. κ°μ π λ¬ΈμλΌλ 무μμ κΈ°μ€μΌλ‘ μΈλλμ λ°λΌ κ²°κ³Όκ° λ¬λΌμ Έμ.
λ¨Όμ λ°μ΄νΈ μ κΈ°μ€μΌλ‘ 보면, πμ UTF-8μμ 4λ°μ΄νΈλ₯Ό μ°¨μ§ν΄μ. μ΄κ±΄ μ μ₯ 곡κ°μ΄λ λ€νΈμν¬ μ μ‘ μ μ©λμ κ³μ°ν λμ κ΄μ μ΄μ£ .
μ½λ μ λ μ κΈ°μ€μΌλ‘ 보면 2μμ. UTF-16μ 16λΉνΈ λ¨μ μ‘°κ°μ μΈλ κ²μ΄κ³ , μλ°μ€ν¬λ¦½νΈμ String.prototype.lengthκ° λ°λ‘ μ΄ κ°μ λ°νν΄μ.
μ½λ ν¬μΈνΈ μ κΈ°μ€μμλ 1μ΄ λΌμ. μ λμ½λμμ πμ νλμ κ³ μ ν μ½λ ν¬μΈνΈ(U+1F344)λ‘ μλ³λλκΉμ.
λ§μ§λ§μΌλ‘ κ·Έλν μ, μ¦ νλ©΄μμ μ¬μ©μκ° μΈμνλ "보μ΄λ κΈμ μ"λ‘ λ³΄λ©΄ μμ 1μ΄μμ. μ΄κ² μ°λ¦¬κ° μΌλ°μ μΌλ‘ "λ¬Έμ νλ"λΌκ³ λλΌλ UX κΈ°μ€μ λ¨μμ£ .
μ 리νμλ©΄, π νλλ μ μ₯ μ 4λ°μ΄νΈ, JSμ .lengthλ‘λ 2, μ λμ½λ λ¬Έμ κΈ°μ€μΌλ‘λ 1, κ·Έλ¦¬κ³ μκ°μ μΌλ‘λ 1κΈμμμ. μ¦, "κΈΈμ΄"λ κΈ°μ μ κ΄μ μ λ°λΌ μ ν λ€λ₯΄κ² μ μλ μ μμ΄μ.
μ€λ¬΄μμ "μ§μ§ κΈμ μ" μΈλ λ°©λ²
μμ¦ μ΄λͺ¨μ§λ€μ μ€ν¨ν€, κ°μ‘±, μ±λ³ μ‘°ν© κ°μ ZWJ μνμ€κ° νν΄μ. λμ 보μ΄λ κΈμ νλκ° μ¬λ¬ μ½λ μ λ λλ μ¬λ¬ μ½λ ν¬μΈνΈλ‘ μ΄λ£¨μ΄μ§ μ μκ±°λ μ.
κ·Έλμ "보μ΄λ κΈμ μ"λ₯Ό μΈλ €λ©΄ μλ λ κ°μ§ λ°©μ μ€ νλλ₯Ό μ¨μΌ ν΄μ.
μ½λ ν¬μΈνΈ κΈ°μ€
[..."πππ½"].length; // 2
const sliceByCodePoints = (str, n) => Array.from(str).slice(0, n).join("");
ES2015 μ΄μμ΄λ©΄ μ΄λμλ μλνλ€λ μ₯μ μ΄ μμ§λ§, ZWJ μ‘°ν©(π¨βπ©βπ§βπ¦)μ μ¬λ¬ κΈμλ‘ μ릴 μ μλ€λ νκ³κ° μμ΄μ.
κ·Έλν κΈ°μ€ (μ¬μ©μ κ΄μ μ μ§μ§ κΈμ μ)
const countGraphemes = (str) => {
const seg = new Intl.Segmenter("ko", { granularity: "grapheme" });
return [...seg.segment(str)].length;
};
const sliceByGraphemes = (str, n) => {
const seg = new Intl.Segmenter("ko", { granularity: "grapheme" });
return [...seg.segment(str)]
.slice(0, n)
.map((s) => s.segment)
.join("");
};
countGraphemes("ππ½"); // 1
μ΅μ λΈλΌμ°μ μ Node(18+) λλΆλΆμμ μ§μν΄μ. λ―Έμ§μ νκ²½μμλ grapheme-splitter ν΄λ¦¬νμ μ°λ©΄ λΌμ.
κ³΅ν΅ μ νΈ ν νλ¦Ώ (νλ‘ νΈ/μλ² κ³΅μ©)
// utils/charLength.ts
export type LengthMode = "grapheme" | "codePoint" | "codeUnit";
export function lengthOf(str: string, mode: LengthMode = "grapheme"): number {
switch (mode) {
case "codeUnit":
return str.length;
case "codePoint":
return Array.from(str).length;
case "grapheme": {
const AnyIntl: any = Intl as any;
if (AnyIntl?.Segmenter) {
const seg = new AnyIntl.Segmenter("ko", { granularity: "grapheme" });
return [...seg.segment(str)].length;
}
const { default: GraphemeSplitter } = require("grapheme-splitter");
return new GraphemeSplitter().countGraphemes(str);
}
}
}
νλ‘ νΈμ μλ²κ° κ°μ μ νΈμ μ°λ©΄ κΈΈμ΄ κ³μ° λΆμΌμΉ λ¬Έμ λ₯Ό μμ² μ°¨λ¨ν μ μμ΄μ.
MySQLμμ λ¬Έμμ΄ κΈΈμ΄λ₯Ό μ λμ ν¨μ κ³Ό λμ²λ²
μλ°μ€ν¬λ¦½νΈμ λ¬Έμμ΄ κΈΈμ΄λ UTF-16 μ½λ μ λ μμ§λ§, MySQLμ λ¬Έμμ΄μ λ°μ΄νΈ(byte) λλ λ¬Έμ(character) κΈ°μ€μΌλ‘ κ³μ°ν΄μ.
μ΄ μ°¨μ΄λ₯Ό νΌλνλ©΄ νΉν μ΄λͺ¨μ§Β·νκΈΒ·νΉμλ¬Έμ μ λ ₯ μ μ μ½ μ‘°κ±΄ μ€λ₯λ λ°μ΄ν° μλ¦Ό κ°μ λ¬Έμ κ° μ½κ² λ°μν΄μ.
LENGTH() β λ°μ΄νΈ μ κΈ°μ€
LENGTH()λ λ¬Έμμ΄μ΄ μ€μ λ‘ μ μ₯μμμ μ°¨μ§νλ λ°μ΄νΈ ν¬κΈ°λ₯Ό λ°νν΄μ. μ¦, κ°μ λ¬Έμμ΄μ΄λΌλ μΈμ½λ©μ λ°λΌ κ²°κ³Όκ° λ¬λΌμ Έμ.
MySQLμ΄ utf8mb4λ₯Ό μΈ λμ μμλ μ΄λμ.
SELECT LENGTH('π'); -- 4
SELECT LENGTH('ν'); -- 3
SELECT LENGTH('A'); -- 1
μ΄λͺ¨μ§λ 4λ°μ΄νΈ, νκΈμ 3λ°μ΄νΈ, ASCII λ¬Έμλ 1λ°μ΄νΈλ₯Ό μ°¨μ§ν΄μ. LENGTH()λ μ»¬λΌ ν¬κΈ°(VARCHAR(255) λ±)λ₯Ό κ³μ°νκ±°λ μ μ₯ μ©λμ νμΈν λλ μ μ©νλ°, μ¬μ©μ μ
λ ₯ κΈΈμ΄ μ νμ²λΌ "κΈμ μ"κ° μ€μν λ‘μ§μλ μ ν©νμ§ μμμ.
CHAR_LENGTH() β λ¬Έμ μ κΈ°μ€
CHAR_LENGTH() (λλ CHARACTER_LENGTH())λ λ¬Έμ κ°μ(μ½λ ν¬μΈνΈ μ) κΈ°μ€μΌλ‘ κΈΈμ΄λ₯Ό λ°νν΄μ.
SELECT CHAR_LENGTH('π'); -- 1
SELECT CHAR_LENGTH('ν'); -- 1
SELECT CHAR_LENGTH('A'); -- 1
μ΄ ν¨μλ μΈμ½λ© λ°©μ(UTF-8, UTF-16, UTF-32)μ μκ΄μμ΄ "μ λμ½λ λ¬Έμ νλ"λ₯Ό 1λ‘ κ³μ°ν΄μ. λ°λΌμ λλ€μ, λκΈ, μ¬μ©μ μ
λ ₯ νλ λ± UX κΈ°μ€μ κΈμ μ κ²μ¦μλ CHAR_LENGTH()λ₯Ό μ°λ κ² μ¬λ°λ₯Έ μ νμ΄μμ.
κ·Έλν λ¨μλ μ¬μ ν λ³κ°λ€
μ£Όμν μ μ, 보μ΄λ κΈμ(κ·Έλν) μ μ λμ½λ λ¬Έμ(μ½λ ν¬μΈνΈ) κ° νμ μΌμΉνμ§ μλλ€λ κ±°μμ. μλ₯Ό λ€μ΄ κ°μ‘± μ΄λͺ¨μ§ π¨βπ©βπ§βπ¦ λ μ¬λ¬ μ½λ ν¬μΈνΈλ₯Ό ZWJ(Zero Width Joiner) λ‘ μ°κ²°ν μ‘°ν©μ΄κ±°λ μ.
SELECT CHAR_LENGTH('π¨βπ©βπ§βπ¦'); -- 7
λμΌλ‘λ "ν κΈμ"μ§λ§, MySQL μ μ₯μμλ 7κ°μ λ¬Έμλ‘ κ³μ°λΌμ. MySQLμ κ·Έλν λ¨μ(μκ°μ μΌλ‘ 보μ΄λ κΈμ μ) λ₯Ό μ΄ν΄νμ§ λͺ»νλκΉμ.
κ²°κ΅ LENGTH()λ μ μ₯ μ©λ(λ°μ΄νΈ) κΈ°μ€μ΄λΌ μΈμ½λ©μ λ°λΌ κ°μ΄ λ¬λΌμ§κ³ , CHAR_LENGTH()λ μ½λ ν¬μΈνΈ κΈ°μ€μΌλ‘ μΌλ°μ μΈ "λ¬Έμ μ" κ³μ°μ μ¨μ. νμ§λ§ κ·Έλν κΈ°μ€μ 보μ΄λ κΈμ μλ DBμμ μ§μνμ§ μμΌλκΉ JSλ μλ²μμ κ³μ°ν΄μΌ ν΄μ. DBμ μ»¬λΌ μ μ½(VARCHAR, TEXT)μ μ μ₯ μ©λ κΈ°μ€μΌλ‘, μ
λ ₯ κ²μ¦μ "보μ΄λ κΈμ μ" κΈ°μ€μΌλ‘ λλμ΄ μ€κ³νλ κ²μ΄ UX κΈ°μ€μ κΈμ μ μ νμ μ νν μ μ©νλ λ°©λ²μ΄μμ.