`String` 객체

`String` 객체

이 문서는 String 객체에 대해 설명합니다.

이 설명은 기본부터 고급 기술, 그리고 유니코드 및 정규식 관련된 함정까지 단계별로 알기 쉽게 다룹니다.

YouTube Video

String 객체

자바스크립트에서 문자열은 일상적인 개발에서 가장 자주 사용되는 타입 중 하나입니다.

원시 문자열과 String 객체의 차이점

원시 문자열(예: "hello")은 new String("hello")와 같은 래퍼 객체와 다르게 동작합니다. 일반적으로 원시 타입을 사용해야 하며 객체 형태를 사용할 필요는 거의 없습니다.

1// Primitive string
2const a = "hello";
3
4// String wrapper object
5const b = new String("hello");
6
7console.log(typeof a); // "string"
8console.log(typeof b); // "object"
9console.log(a === b);  // false — wrapper objects are not strictly equal
  • 이 코드는 원시 값과 래퍼 객체의 타입 차이와, 엄격 비교에서 어떻게 동작하는지 보여줍니다. 대부분의 경우 new String() 사용을 피하고 원시값을 사용하세요.

문자열 생성 방법 (리터럴과 템플릿 리터럴)

템플릿 리터럴은 변수를 삽입하거나 여러 줄의 문자열을 작성할 때 유용합니다. 변수를 삽입하고, 표현식을 직관적으로 계산할 수 있습니다.

1const name = "Alice";
2const age = 30;
3
4// Template literal
5const greeting = `Name: ${name}, Age: ${age + 1}`;
6
7console.log(greeting); // "Name: Alice, Age: 31"
  • 템플릿 리터럴은 가독성이 높아 복잡한 문자열과 여러 줄 문자열 작성에 적합합니다.

공통 메서드 (검색과 부분 문자열 추출)

String 객체에는 다양한 기본 메서드가 있습니다.

 1const text = "Hello, world! Hello again.";
 2
 3// search
 4console.log(text.indexOf("Hello"));       // 0
 5console.log(text.indexOf("Hello", 1));    // 13
 6console.log(text.includes("world"));      // true
 7console.log(text.startsWith("Hello"));    // true
 8console.log(text.endsWith("again."));     // true
 9
10// slice / substring
11console.log(text.slice(7, 12));           // "world"
12console.log(text.substring(7, 12));       // "world"
  • slicesubstring은 비슷하지만, 음수 인덱스 처리 방식이 다릅니다. slice는 음수 값을 끝에서부터의 위치로 해석합니다. 어떤 것을 사용할지 분명히 정하세요.

분할과 결합 (split / join)

문자열을 배열로 분할하여 처리한 뒤, 다시 합치는 것이 일반적입니다.

1const csv = "red,green,blue";
2const arr = csv.split(","); // ["red","green","blue"]
3
4console.log(arr);
5console.log(arr.join(" | ")); // "red | green | blue"
  • split으로 분할하고, map이나 filter로 배열을 처리한 후, join으로 다시 결합하는 패턴이 흔히 사용됩니다.

치환 및 정규 표현식

replace는 첫 번째 일치 항목만 치환합니다. 모든 일치 항목을 치환하려면 정규식에 g 플래그를 사용하세요. 치환값에 함수를 넘기면 동적으로 치환 처리가 가능합니다.

 1const s = "foo 1 foo 2";
 2
 3// replace first only
 4console.log(s.replace("foo", "bar")); // "bar 1 foo 2"
 5
 6// replace all using regex
 7console.log(s.replace(/foo/g, "bar")); // "bar 1 bar 2"
 8
 9// replace with function
10const r = s.replace(/\d+/g, (match) => String(Number(match) * 10));
11console.log(r);    // "foo 10 foo 20"
  • 함수를 이용한 동적 치환으로, 일치 항목을 분석하고 변환하는 코드를 간결하게 작성할 수 있습니다.

대소문자 변환과 정규화

toLowerCasetoUpperCase뿐만 아니라 다국어 지원과 비교를 위해서는 유니코드 정규화(normalize)도 중요합니다. 특히 악센트가 있는 문자 비교 시에 반드시 필요합니다.

 1// Case conversion example:
 2// "\u00DF" represents the German letter "ß".
 3// In some locales, converting "ß" to uppercase becomes "SS".
 4// JavaScript follows this behavior.
 5console.log("\u00DF");
 6console.log("\u00DF".toUpperCase()); // "SS"
 7
 8// Unicode normalization example:
 9// "e\u0301" is "e" + a combining acute accent.
10// "\u00e9" is the precomposed character "é".
11// These two look the same but are different code point sequences.
12const a = "e\u0301";
13const b = "\u00e9";
14
15console.log(a === b);   // false: different underlying code points
16console.log(a.normalize() === b.normalize()); // true: normalized to the same form
  • 합자나 결합 문자 등 유니코드 인코딩 방식이 다르면 동일하지 않으므로, 비교 전에 반드시 normalize()를 사용하세요.

유니코드와 코드 포인트 (서로게이트 페어 처리)

자바스크립트 문자열은 UTF-16 코드 단위들의 시퀀스이므로, 이모지 등 일부 문자가 한 글자에 두 개의 코드 단위를 차지할 수 있습니다. 실제 문자 단위로 처리하려면 Array.from, 전개 연산자, 또는 for...of를 사용하세요.

 1// Emoji composed with multiple code points:
 2// "\u{1F469}" = woman, "\u{200D}" = Zero Width Joiner (ZWJ),
 3// "\u{1F4BB}" = laptop. Combined, they form a single emoji: 👩‍💻
 4const s = "\u{1F469}\u{200D}\u{1F4BB}";
 5console.log(s);
 6
 7// Length in UTF-16 code units (not actual Unicode characters):
 8// Because this emoji uses surrogate pairs + ZWJ, the length may be > 1.
 9console.log("Length:", s.length);
10
11// Iterate by Unicode code points (ES6 for...of iterates code points):
12// Each iteration gives a full Unicode character, not UTF-16 units.
13for (const ch of s) {
14  console.log(ch);
15}
16
17// Convert to an array of Unicode characters:
18console.log(Array.from(s));
  • length는 코드 단위의 수를 반환하므로, 이모지나 합자에서는 기대한 글자 수가 나오지 않습니다. for...ofArray.from은 화면에 보이는 문자 단위(그래핀 클러스터)에 가깝게 처리하지만, 완전한 지원이 필요하다면 전용 라이브러리를 사용하는 것을 권장합니다.

안전한 정규 표현식 치환 (사용자 입력 처리 시)

사용자 입력을 정규식에 삽입할 때 이스케이프 처리를 잊으면 예기치 않은 동작과 취약점이 발생할 수 있습니다. 항상 패턴에 사용하기 전에 사용자 입력을 이스케이프하세요.

1function escapeRegex(s) {
2  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3}
4
5const userInput = "a+b";
6const pattern = new RegExp(escapeRegex(userInput), "g");
7console.log("a+b a+b".replace(pattern, "X")); // "X X"
  • 사용자 입력 문자열을 정규표현식에 직접 사용하지 말고, 항상 이스케이프 처리 후에 생성하세요.

성능 팁: 연결과 템플릿

여러 개의 작은 문자열을 순차적으로 연결할 때, 배열에 넣고 join을 사용하는 것이 더 효율적일 수 있습니다. 반면, 템플릿 문자열은 가독성이 우수하며 대부분의 경우 성능도 충분히 빠릅니다.

 1// concatenation in loop (less ideal)
 2let s = "";
 3for (let i = 0; i < 1000; i++) {
 4  s += i + ",";
 5}
 6
 7// using array + join (often faster for many pieces)
 8const parts = [];
 9for (let i = 0; i < 1000; i++) {
10  parts.push(i + ",");
11}
12const s2 = parts.join("");
  • 최신 자바스크립트 엔진은 최적화가 잘 되어 있어, 적은 횟수의 연결에서는 성능을 걱정할 필요가 없습니다. 하지만 수만 번 이상 연결이 필요하다면 join을 사용하는 것이 더 효율적일 수 있습니다.

실용적인 기법: 패딩, 트림, 반복

trim, padStart, padEnd, 그리고 repeat는 일상적인 문자열 처리에서 특히 유용한 편리한 메서드입니다. 입력 값을 포맷하거나 출력 형식을 표준화하는 등 실용적인 상황에서 자주 사용됩니다.

1console.log("  hello  ".trim());       // "hello"
2console.log("5".padStart(3, "0"));     // "005"
3console.log("x".repeat(5));            // "xxxxx"
  • 이 메서드들은 폼 입력을 정규화하거나 고정 폭 출력을 생성할 때 사용할 수 있습니다.

문자열 비교 (로케일 비교)

localeCompare는 다양한 언어의 사전 순으로 문자열을 비교할 때 효과적입니다. 언어와 민감도(대소문자 구분 등) 옵션을 지정할 수 있습니다.

1console.log(
2  "\u00E4".localeCompare("z", "de")
3); // may be -1 or other depending on locale
4
5console.log(
6  "a".localeCompare("A", undefined, { sensitivity: "base" })
7); // 0
  • 국제화된 비교에서는 localeCompare를 사용하고 적절한 로케일 및 옵션을 지정하세요.

실전 예제: CSV 행을 객체로 변환하기 (실무 워크플로우)

일반적인 사용 예는 split, trim, map을 조합하여 CSV 한 행을 객체로 파싱하는 것입니다. 따옴표 필드나 복잡한 CSV 파일의 경우에는 전용 CSV 파서를 사용하세요.

 1// simple CSV parse (no quotes handling)
 2function parseCsvLine(line, headers) {
 3  const values = line.split(",").map(v => v.trim());
 4  const obj = {};
 5  headers.forEach((h, i) => obj[h] = values[i] ?? null);
 6  return obj;
 7}
 8
 9const headers = ["name", "age", "city"];
10const line = " Alice , 30 , New York ";
11console.log(parseCsvLine(line, headers));
12// { name: "Alice", age: "30", city: "New York" }
  • 이 방법은 간단한 CSV에서는 동작하지만, 필드 내에 쉼표가 포함된 경우는 처리할 수 없습니다.

자주 발생하는 함정

자바스크립트 문자열 처리에는 쉽게 간과하는 사양과 동작이 있습니다. 예상치 못한 버그를 피하려면 다음 사항을 반드시 기억해야 합니다.

  • new String()을 사용하면 타입 검사나 비교 시 올바르지 않은 결과가 나올 수 있습니다. 대부분의 경우, 원시 문자열 타입이면 충분합니다.
  • 유니코드에서는 하나의 눈에 보이는 문자가 여러 코드 단위로 구성될 수 있습니다. 따라서 length로 반환되는 값이 실제 표시되는 문자 수와 다를 수 있습니다.
  • 사용자 입력을 정규표현식에 포함할 때는 반드시 먼저 이스케이프 처리하세요.
  • String.prototype.replace()는 기본적으로 첫 번째 일치 항목만 치환합니다. 모든 항목을 교체하려면 정규식에 /g 플래그를 사용하세요.
  • 문자열은 불변이므로, 연산 시 항상 새로운 문자열을 반환합니다. 반환된 값을 반드시 할당하는 것이 중요합니다.

요약

자바스크립트 문자열은 단순해 보이더라도, 유니코드와 불변성에 대한 특성을 이해하는 것이 중요합니다. 기본기를 마스터하면 문자열 처리의 신뢰성과 가독성을 크게 높일 수 있습니다.

위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.

YouTube Video