βπβ.length === 2? μλ°μ€ν¬λ¦½νΈμμ μ΄λͺ¨μ§ λ¬Έμμ΄ κΈΈμ΄μ ν¨μ κ³Ό ν΄κ²°λ²
2025-10-08νλ‘ νΈμλμμ λ¬Έμμ΄ κΈΈμ΄λ₯Ό κ²μ¬νλ€ λ³΄λ©΄ μ΄λ° κ²½νμ΄ μμ΅λλ€.
βμ΄λͺ¨μ§ νλλ₯Ό λ£μλλ° κΈΈμ΄κ° 2λ‘ κ³μ°λΌ?β
'π'.length // 2
μ΄κ±΄ μλ°μ€ν¬λ¦½νΈμ λ¬Έμμ΄ λͺ¨λΈμ΄ μ λμ½λμ λ§λ¬Όλ¦¬λ μ§μ μμ μκΈ°λ, μμ£Ό ννμ§λ§ ν·κ°λ¦¬λ λ¬Έμ μ λλ€.
1. μ βπβ.lengthλ 2μΌκΉ?
μλ°μ€ν¬λ¦½νΈ λ¬Έμμ΄μ UTF-16 μ½λ μ λ λ¨μλ‘ μ μ₯λ©λλ€.
μ¦, String.prototype.lengthλ μ€μ β보μ΄λ κΈμ μβκ° μλλΌ β16λΉνΈ μ‘°κ° κ°μβλ₯Ό λ°ννμ£ .
"π" (U+1F344)μ BMP(κΈ°λ³Έ λ€κ΅μ΄ νλ©΄) λ°μ λ¬ΈμλΌ 16λΉνΈ λ μ‘°κ°(μλ‘κ²μ΄νΈ νμ΄)λ‘ ννλ©λλ€.
"π".length; // 2
"π".charCodeAt(0).toString(16); // "d83c"
"π".charCodeAt(1).toString(16); // "df44"
"π".codePointAt(0).toString(16); // "1f344"
κ²°κ΅ 'π'μ μ½λ μ λ 2κ°λ‘ ꡬμ±λλ―λ‘ .length === 2κ° λ©λλ€.
μ’μμ π ν λμ μμ°μ€λ¬μ΄ λ¬Έλ¨ ννλ‘ μ 리νλ©΄ μ΄λ κ² ννν μ μμ΅λλ€ π
2. βκΈΈμ΄βλ 무μμ μΈλλμ λ°λΌ λ¬λΌμ§λ€
μλ°μ€ν¬λ¦½νΈμμ λ¬Έμμ΄μ βκΈΈμ΄βλ μκ°λ³΄λ€ μ¬λ¬ μΈ΅μλ‘ λλ μ§λλ€. κ°μ π λ¬ΈμλΌλ 무μμ κΈ°μ€μΌλ‘ μΈλλμ λ°λΌ κ²°κ³Όκ° λ¬λΌμ§λλ€.
λ¨Όμ λ°μ΄νΈ μλ₯Ό κΈ°μ€μΌλ‘ 보면, πμ UTF-8μμ 4λ°μ΄νΈλ₯Ό μ°¨μ§ν©λλ€. μ΄κ±΄ μ μ₯ 곡κ°μ΄λ λ€νΈμν¬ μ μ‘ μ μ©λμ κ³μ°ν λμ κ΄μ μ΄μ£ .
μ½λ μ λ μ κΈ°μ€μΌλ‘ 보면 2μ
λλ€.
μ΄λ UTF-16μ 16λΉνΈ λ¨μ μ‘°κ°μ μΈλ κ²μΌλ‘, μλ°μ€ν¬λ¦½νΈμ String.prototype.lengthκ° λ°λ‘ μ΄ κ°μ λ°νν©λλ€.
μ½λ ν¬μΈνΈ μ κΈ°μ€μμλ 1μ΄ λ©λλ€. μ λμ½λμμ πμ νλμ κ³ μ ν μ½λ ν¬μΈνΈ(U+1F344)λ‘ μλ³λκΈ° λλ¬Έμ λλ€.
λ§μ§λ§μΌλ‘ κ·Έλν μ, μ¦ νλ©΄μμ μ¬μ©μκ° μΈμνλ β보μ΄λ κΈμ μβλ‘ λ³΄λ©΄ μμ 1μ λλ€. μ΄κ² μ°λ¦¬κ° μΌλ°μ μΌλ‘ βλ¬Έμ νλβλΌκ³ λλΌλ UX κΈ°μ€μ λ¨μμ λλ€.
μ 리νμλ©΄, π νλλ μ μ₯ μ 4λ°μ΄νΈ, JSμ .lengthλ‘λ 2,
μ λμ½λ λ¬Έμ κΈ°μ€μΌλ‘λ 1, κ·Έλ¦¬κ³ μκ°μ μΌλ‘λ 1κΈμμ
λλ€.
μ¦, βκΈΈμ΄βλ κΈ°μ μ κ΄μ μ λ°λΌ μ ν λ€λ₯΄κ² μ μλ μ μμ΅λλ€.
3. μ€λ¬΄μμ βμ§μ§ κΈμ μβ μΈλ λ°©λ²
μμ¦ μ΄λͺ¨μ§λ€μ μ€ν¨ν€, κ°μ‘±, μ±λ³ μ‘°ν© κ°μ ZWJ μνμ€κ° νν©λλ€. λμ 보μ΄λ κΈμ νλκ° μ¬λ¬ μ½λ μ λ λλ μ¬λ¬ μ½λ ν¬μΈνΈλ‘ μ΄λ£¨μ΄μ§ μ μμ£ .
κ·Έλμ β보μ΄λ κΈμ μβλ₯Ό μΈλ €λ©΄ μλ λ κ°μ§ λ°©μ μ€ νλλ₯Ό μ¨μΌ ν©λλ€.
1) μ½λ ν¬μΈνΈ κΈ°μ€
[..."πππ½"].length; // 2
const sliceByCodePoints = (str, n) => Array.from(str).slice(0, n).join("");
μ₯μ : ES2015 μ΄μμ΄λ©΄ μ΄λμλ μλ
λ¨μ : ZWJ μ‘°ν©(π¨βπ©βπ§βπ¦)μ μ¬λ¬ κΈμλ‘ μ릴 μ μμ
2) κ·Έλν κΈ°μ€ (μ¬μ©μ κ΄μ μ μ§μ§ κΈμ μ)
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 ν΄λ¦¬νμ μ¬μ©νμΈμ.
4. κ³΅ν΅ μ νΈ ν νλ¦Ώ (νλ‘ νΈ/μλ² κ³΅μ©)
// 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);
}
}
}
νλ‘ νΈμ μλ²κ° κ°μ μ νΈμ μ¬μ©νλ©΄ κΈΈμ΄ κ³μ° λΆμΌμΉ λ¬Έμ λ₯Ό μμ² μ°¨λ¨ν μ μμ΅λλ€.
5. MySQLμμ λ¬Έμμ΄ κΈΈμ΄λ₯Ό μ λμ ν¨μ κ³Ό λμ²λ²
μλ°μ€ν¬λ¦½νΈμ λ¬Έμμ΄ κΈΈμ΄λ UTF-16 μ½λ μ λ μμ§λ§, MySQLμ λ¬Έμμ΄μ λ°μ΄νΈ(byte) λλ λ¬Έμ(character) κΈ°μ€μΌλ‘ κ³μ°ν©λλ€.
μ΄ μ°¨μ΄λ₯Ό νΌλνλ©΄, νΉν μ΄λͺ¨μ§Β·νκΈΒ·νΉμλ¬Έμ μ λ ₯ μ μ μ½ μ‘°κ±΄ μ€λ₯λ λ°μ΄ν° μλ¦Ό κ°μ λ¬Έμ κ° μ½κ² λ°μν©λλ€.
1) LENGTH() β λ°μ΄νΈ μ κΈ°μ€
LENGTH()λ λ¬Έμμ΄μ΄ μ€μ λ‘ μ μ₯μμμ μ°¨μ§νλ λ°μ΄νΈ ν¬κΈ°λ₯Ό λ°νν©λλ€.
μ¦, κ°μ λ¬Έμμ΄μ΄λΌλ μΈμ½λ©μ λ°λΌ κ²°κ³Όκ° λ¬λΌμ§λλ€.
MySQLμ΄ utf8mb4λ₯Ό μ¬μ©ν λμ μμλ λ€μκ³Ό κ°μ΅λλ€.
SELECT LENGTH('π'); -- 4
SELECT LENGTH('ν'); -- 3
SELECT LENGTH('A'); -- 1
- π(μ΄λͺ¨μ§): 4λ°μ΄νΈ
- νκΈ: 3λ°μ΄νΈ
- ASCII λ¬Έμ: 1λ°μ΄νΈ
LENGTH()λ μ»¬λΌ ν¬κΈ°(VARCHAR(255) λ±)λ₯Ό κ³μ°νκ±°λ μ μ₯ μ©λμ νμΈν λλ μ μ©νμ§λ§,
μ¬μ©μ μ
λ ₯ κΈΈμ΄ μ νμ²λΌ βκΈμ μβκ° μ€μν λ‘μ§μλ μ ν©νμ§ μμ΅λλ€.
2) 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()λ₯Ό μ¬μ©νλ κ²μ΄ μ¬λ°λ₯Έ μ νμ
λλ€.
3) κ·Έλν λ¨μλ μ¬μ ν λ³κ°λ€
μ£Όμν μ μ, 보μ΄λ κΈμ(κ·Έλν) μ μ λμ½λ λ¬Έμ(μ½λ ν¬μΈνΈ) κ° νμ μΌμΉνμ§ μλλ€λ κ²μ λλ€. μλ₯Ό λ€μ΄ κ°μ‘± μ΄λͺ¨μ§ π¨βπ©βπ§βπ¦ λ μ¬λ¬ μ½λ ν¬μΈνΈλ₯Ό ZWJ(Zero Width Joiner) λ‘ μ°κ²°ν μ‘°ν©μ λλ€.
SELECT CHAR_LENGTH('π¨βπ©βπ§βπ¦'); -- 7
λμΌλ‘λ βν κΈμβμ§λ§, MySQL μ μ₯μμλ 7κ°μ λ¬Έμλ‘ κ³μ°λ©λλ€. μ¦, MySQLμ κ·Έλν λ¨μ(μκ°μ μΌλ‘ 보μ΄λ κΈμ μ) λ₯Ό μ΄ν΄νμ§ λͺ»ν©λλ€.
UX κΈ°μ€μ βκΈμ μ μ νβμ μ νν μ μ©νλ €λ©΄
DBκ° μλ μ ν리μΌμ΄μ
λ 벨μμ μ²λ¦¬ν΄μΌ ν©λλ€.
(Intl.Segmenter λλ grapheme-splitterλ₯Ό μ¬μ©)
4) μ 리
LENGTH()β μ μ₯ μ©λ(λ°μ΄νΈ) κΈ°μ€. μΈμ½λ©μ λ°λΌ κ°μ΄ λ¬λΌμ§.CHAR_LENGTH()β μ½λ ν¬μΈνΈ κΈ°μ€. μΌλ°μ μΈ βλ¬Έμ μβ κ³μ°μ μ¬μ©.- κ·Έλν κΈ°μ€(보μ΄λ κΈμ μ) μ DBμμ μ§μνμ§ μμΌλ―λ‘ JS/μλ²μμ κ³μ°.
- DBμ μ»¬λΌ μ μ½(
VARCHAR,TEXT)μ μ μ₯ μ©λ κΈ°μ€μΌλ‘, μ λ ₯ κ²μ¦μ β보μ΄λ κΈμ μβ κΈ°μ€μΌλ‘ λλμ΄ μ€κ³νμ.
μμ½νμλ©΄,
MySQLμ β보μ΄λ κΈμ μβλ₯Ό λͺ¨λ₯Έλ€. >
LENGTH()λ μ μ₯ 곡κ°,CHAR_LENGTH()λ λ¬Έμ κ°μ.UX κΈ°μ€μ κΈΈμ΄ μ νμ κ²°κ΅ μ ν리μΌμ΄μ μ λͺ«μ΄λ€.