문자열의 기본 특성
JavaScript에서 문자열은 원시 타입 중 하나이며, 변경 불가능(immutable) 합니다. 즉, 한 번 생성된 문자열을 직접 수정할 수 없고, 수정된 '새로운 문자열'을 만들어서 반환합니다.
String 객체와 원시 문자열
원시 문자열(primitive string)
let str = "Hello";
이는 JS 엔진 내부적으로 문자열 리터럴을 저장하며, 상수 풀(String pool)에 캐싱될 수도 있습니다.
String 객체
let objStr = new String("Hello");
이는 래퍼(wrapper) 객체입니다. 원시 문자열에 대해 객체 형태의 프로퍼티와 메서드를 사용할 때 내부적으로 임시 String
객체로 감싸주지만, 가급적 직접 new String(...)
방식보다는 원시 문자열 리터럴을 사용하는 것이 권장됩니다.
- 타입 혼동(Type Confusion)
String
객체는typeof
연산자로 검사할 때 object로 반환됩니다.- 반면 원시 문자열은
typeof
로 string을 반환합니다. - 문제점 :
==
비교는 같다고 판단할 수 있지만,===
는 타입이 다르기 때문에false
가 됩니다. 이는 의도치 않은 버그를 유발할 가능성이 있습니다.
- 불필요한 메모리 및 성능 문제
String
객체를 생성하면, 내부적으로 더 많은 메모리를 소비하며, 실제로는 간단한 텍스트 조작을 위해 불필요한 오버헤드가 발생합니다.- 원시 문자열은 필요한 경우, 자동으로 임시
String
객체로 래핑되기 때문에 굳이 명시적으로new String()
을 호출할 필요가 없습니다.
- API 호환성 및 예상치 못한 동작
new String("")
은 객체이기 때문에 비어 있어도 truthy로 평가됩니다.- 반면
""
(빈 문자열)은 falsy입니다. - 따라서
if (str)
같은 조건문에서String
객체는 항상 true로 평가됩니다.
function checkString(str) {
if (str) {
console.log("String is not empty");
}
}
checkString(new String("")); // "String is not empty" (비어 있음에도 불구하고 실행됨)
checkString(""); // 아무것도 출력되지 않음 (정상 동작)
문자열과 배열의 유사점과 차이점
문자열도 인덱스로 각 문자를 접근할 수 있어 배열처럼 보이지만(str[0]
), 실제로는 불변이고. length
이외에 배열에서 제공하는 여러 메서드(push
, pop
, splice
등)는 사용할 수 없습니다.
for...of
문을 이용해 문자열의 각 문자를 순회할 수 있습니다.
let str = "ABC";
for (let ch of str) {
console.log(ch);
}
// A
// B
// C
문자열 생성과 템플릿 리터럴
문자열 리터럴(Literal)
- 작은따옴표
' '
- 큰따옴표
" "
- 백틱(ES6 템플릿 리터럴) `
let single = 'Hello';
let double = "Hello";
let backtick = `Hello`;
템플릿 리터럴(Template Literal)
ES6에서 도입된 문법으로, 표현식 삽입과 여러 줄 문자열을 간편하게 작성할 수 있습니다.
let name = "John";
let greeting = `Hello, ${name}!
Welcome to JavaScript.`;
또한 태그드 템플릿(Tagged Template) 기능을 이용하면, 템플릿 리터럴 내부의 문자열과 표현식을 분리해 특별한 처리를 할 수도 있습니다.
function highlight(strings, ...values) {
// strings: ['My name is ', ' and I am ', ' years old.']
// values: ['John', 20]
return strings.reduce((acc, str, i) => {
return acc + str + (values[i] ? `<strong>${values[i]}</strong>` : "");
}, "");
}
let name2 = "John";
let age = 20;
let result = highlight`My name is ${name2} and I am ${age} years old.`;
console.log(result);
// My name is <strong>John</strong> and I am <strong>20</strong> years old.
문자열 메서드 총정리
검색 / 판별
메서드 | 설명 |
indexOf(substr) |
문자열 substr 를 찾고, 첫 번째 인덱스를 반환 (없으면 -1) |
lastIndexOf(substr) |
문자열 substr 를 뒤에서부터 찾고, 인덱스를 반환 (없으면 -1) |
includes(substr) |
문자열에 substr 가 포함되어 있는지 여부 (true/false) |
startsWith(substr) |
문자열이 substr 로 시작하는지 여부 (true/false) |
endsWith(substr) |
문자열이 substr 로 끝나는지 여부 (true/false) |
search(regexp) |
정규 표현식 regexp 에 매칭되는 첫 번째 인덱스 반환 (없으면 -1) |
match(regexp) |
정규 표현식 regexp 와 매칭되는 결과(배열 혹은 null) 반환 |
matchAll(regexp) |
정규 표현식 regexp 에 매칭되는 모든 결과(이터레이터) 반환 (ES2020) |
matchAll 예시
let str = "test1test2";
let regexp = /t(e)(s)(t)/g;
let matches = str.matchAll(regexp);
for (let match of matches) {
console.log(match);
// 예: ["test", "e", "s", "t", index: 0, input: "test1test2", groups: undefined]
// ["test", "e", "s", "t", index: 5, input: "test1test2", groups: undefined]
}
추출
메서드 | 설명 |
charAt(index) |
해당 인덱스 위치의 문자 반환 (인덱스 벗어나면 빈 문자열 반환) |
charCodeAt(index) |
해당 인덱스 위치 문자의 UTF-16 코드 유닛 값(0–65535 사이 정수) 반환 |
codePointAt(index) |
UTF-16이 아닌 유니코드 코드 포인트(서로게이트 문자 포함)에 안전하게 접근 가능 (ES6) |
slice(start, end) |
문자열의 일부를 추출해 새 문자열 반환 (음수 인덱스 가능) |
substring(start, end) |
start 와 end 사이의 문자열을 반환 (음수는 0으로 처리) |
substr(start, length) |
start 부터 length 길이만큼 반환 (현재는 비추천 Deprecated) |
let str = "JavaScript 💖";
console.log(str.charAt(4)); // S
console.log(str.slice(0, 4)); // Java
console.log(str.substring(4, 10)); // Script
console.log(str.codePointAt(11)); // 💖의 유니코드 코드 포인트
substr()
는 과거부터 사용되어 왔지만, 현대는 Deprecated 상태이므로 가능한 slice()
나 substring()
을 사용하시는 것이 좋습니다.
변환
메서드 | 설명 |
toUpperCase() |
모두 대문자로 변환 |
toLowerCase() |
모두 소문자로 변환 |
toLocaleUpperCase() |
지역화(로케일)에 맞춰 대문자로 변환 (예: 터키어 i → İ) |
toLocaleLowerCase() |
지역화(로케일)에 맞춰 소문자로 변환 |
normalize(form) |
유니코드 정규화 (NFC, NFD, NFKC, NFKD 등) |
// 예: 한글 “가”는 유니코드 U+AC00, 또는 자모를 결합한 형태로도 표현 가능
let str1 = "가";
let str2 = "\u1100\u1161"; // ㄱ + ㅏ 조합
console.log(str1 === str2); // false, 다른 코드 포인트 시퀀스
console.log(str1.normalize() === str2.normalize()); // true
조작
메서드 | 설명 |
replace(찾을값, 바꿀값) |
첫 번째로 일치하는 패턴만 바꿔서 새 문자열 반환. 정규 표현식 가능 |
replaceAll(찾을값, 바꿀값) |
일치하는 모든 패턴을 바꿔서 새 문자열 반환 (ES2021) |
trim() |
양쪽 공백(개행 문자 포함) 제거 |
trimStart() / trimEnd() |
문자열 앞/뒤 공백만 제거 (이전 이름은 trimLeft() , trimRight() ) |
padStart(targetLength, padStr) |
문자열 길이가 targetLength 가 될 때까지 앞쪽에 padStr 를 반복해서 붙인 새 문자열 반환 |
padEnd(targetLength, padStr) |
문자열 길이가 targetLength 가 될 때까지 뒤쪽에 padStr 를 반복해서 붙인 새 문자열 반환 |
let str = " Hello JavaScript! ";
console.log(str.replace("Java", "Type")); // " Hello TypeScript! "
console.log(str.replaceAll(" ", "*")); // "**Hello*JavaScript!**"
console.log(str.trim()); // "Hello JavaScript!"
console.log(str.padStart(25, "*")); // "***** Hello JavaScript!"
분리 / 결합 / 반복
메서드 | 설명 |
split(separator) |
separator 로 분리하여 배열 반환 |
concat(...strs) |
여러 문자열을 결합해 새 문자열 반환 |
repeat(count) |
문자열을 count 번 반복해 이어붙인 새 문자열 반환 |
localeCompare() |
현재 문자열과 인자로 받은 문자열을 비교하여 -1, 0, 1로 결과 반환 (정렬 시 유용) |
let fruit = "apple,orange,banana";
console.log(fruit.split(",")); // ['apple', 'orange', 'banana']
console.log("Hello".concat(" World!")); // "Hello World!"
console.log("hi ".repeat(3)); // "hi hi hi "
console.log("b".localeCompare("a")); // 1 (사전순으로 b > a)
고급 주제
서로게이트 쌍(Surrogate Pair)와 이모지(Emoji) 다루기
JS 문자열은 내부적으로 UTF-16 코드 유닛(0 ~ 65536) 기반으로 관리합니다. 유니코드가 65535(0xFFFF) 범위를 넘는 문자(예: 이모지, 일부 한자)는 서로게이트 쌍(Surrogate Pair)으로 표현됩니다. 따라서 길이가 2인 문자도 있고, charAt
과 charCodeAt
만으로는 제대로 처리하기 어려울 수 있습니다. ES6 이후 제공되는 codePointAt
, fromCodePoint
메서드를 사용하면 비교적 안전하게 이모지 등을 다룰 수 있습니다.
let smile = "😊";
console.log(smile.length); // 2, 서로게이트 쌍
console.log(smile.charAt(0)); // � (깨진 문자)
console.log(smile.codePointAt(0)); // 128522 (UTF-32 코드 포인트)
// 문자열 생성
let heart = String.fromCodePoint(0x1F49C);
console.log(heart); // 💜
정규 표현식(Regular Expression)과 함께 쓰기
문자열 검색, 치환, 추출 등을 정규 표현식과 함께 사용하면 더 강력합니다. match
, matchAll
, search
, replace
, replaceAll
, split
등에서 정규 표현식을 사용할 수 있습니다.
let text = "123-456-7890";
let result = text.replace(/\D/g, ""); // 숫자 이외(\D)를 모두 제거
console.log(result); // "1234567890"
국제화(i18n)와 지역화(l10n)
문자열을 비교하거나 대소문자로 변환할 때, 언어와 문화권(로케일, locale)에 따라 결과가 달라질 수 있습니다. toLocaleUpperCase(locale)
, toLocaleLowerCase(locale)
, localeCompare(locale, options)
등을 적절히 활용하면 정확한 정렬, 비교, 변환이 가능합니다.
let turkishI = "ı"; // 터키 소문자
console.log(turkishI.toUpperCase()); // I (영문)
console.log(turkishI.toLocaleUpperCase("tr-TR"));// İ (터키 문자)
성능과 메모리
문자열은 불변이므로, 많은 수의 문자열을 조합하거나 잘라낼 때, 잦은 새 문자열 생성은 비용이 커질 수 있습니다. 대량의 문자열을 다룰 때는 배열에 담아 두었다가 최종적으로 join
으로 합치는 방식을 자주 사용합니다. (예: 로그 누적, 긴 HTML 문자열 작성)
let builder = [];
for (let i = 0; i < 1000; i++) {
builder.push(`Line ${i}`);
}
let finalStr = builder.join("\n");
마무리
정리하자면, JavaScript의 String
(원시 문자열)은 다루기 쉽고 강력한 기능을 제공하지만, 불변 구조, 유니코드 이슈, 정규 표현식 활용 같은 세부 사항을 잘 이해해야 버그 없이 안정적으로 사용할 수 있습니다. 위 내용을 토대로 다양한 문자열 처리 상황에 맞춰 적절히 활용해 보세요.
'JAVASCRIPT' 카테고리의 다른 글
[React] Virtual DOM: 리액트의 핵심 기술 (0) | 2025.01.09 |
---|---|
[React] 리액트적으로 사고하기(번역) (1) | 2025.01.01 |
자바스크립트와 브라우저의 비동기 처리 구조: 이벤트 루프와 큐 (1) | 2024.12.28 |
배열 생성 심화: Array / Array.of / Array.from (0) | 2024.12.24 |
자바스크립트 배열(Array)의 모든 것 (0) | 2024.12.21 |