'π'.length === 2? μλ°μ€ν¬λ¦½νΈμμ μ΄λͺ¨μ§ λ¬Έμμ΄ κΈΈμ΄μ ν¨μ κ³Ό ν΄κ²°λ²
κΈμ μ μ νμ κ±°λ μ½λλ μΈμμμ μ μΌ λ¨μν μ½λλΌκ³ μκ°νμ΅λλ€. str.lengthμ μ«μ νλλ©΄ λλλ μΌμ΄λκΉμ. νλ‘ νΈμμ κ²μ¬νκ³ , μλ²μμ ν λ² λ κ²μ¬νκ³ , DB μ»¬λΌ ν¬κΈ°λ§ λ§μΆ°λλ©΄ λλ μ€ μμμ΅λλ€.
κ·Έλ¬λ€ μ΄λͺ¨μ§ νλκ° λ€μ΄μ΅λλ€.
"μ΄λͺ¨μ§ νλλ₯Ό λ£μλλ° κΈΈμ΄κ° 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);
}
}
}
νλ‘ νΈμ μλ²κ° κ°μ μ νΈμ μ°λ©΄ κΈΈμ΄ κ³μ° λΆμΌμΉ λ¬Έμ λ₯Ό μμ² μ°¨λ¨ν μ μμ΅λλ€.
DBλ λ λ€λ₯Έ κΈ°μ€μΌλ‘ μΌλ€
μλ°μ€ν¬λ¦½νΈμ λ¬Έμμ΄ κΈΈμ΄λ 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)μ μ μ₯ μ©λ κΈ°μ€μΌλ‘, μ
λ ₯ κ²μ¦μ "보μ΄λ κΈμ μ" κΈ°μ€μΌλ‘ λλμ΄ μ€κ³νλ μͺ½μ΄ λ§λ€κ³ μκ°νκ² λμ΅λλ€. μ§κΈ λ§λ€κ³ μλ μ
λ ₯ νΌμ΄ μλ€λ©΄, κ±°κΈ°μ μ΄λͺ¨μ§ νλλ₯Ό λ£μ΄λ³΄λ©΄ μ’κ² μ΅λλ€. μ΄λ€ μλ‘ μ¬κ³ μμλμ§κ° λ°λ‘ λλ¬λ μ§λ λͺ¨λ¦
λλ€.