1.문자열은 글자의 줄입니다
2강에서 본 것처럼 문자열(string)은 따옴표로 감싼 값입니다. 큰따옴표 "사과", 작은따옴표 '사과' 둘 다 같은 문자열이고, 백틱으로 감싸는 템플릿 문자열(템플릿 리터럴)은 문자열 안에 변수 값을 끼워 넣을 때 씁니다. 템플릿 문자열의 모양은 아래 코드에서 직접 확인하세요. 어느 따옴표를 쓰든 한 프로그램 안에서는 하나로 맞추는 편이 읽기 좋습니다.
문자열은 글자가 순서대로 늘어선 줄이라서 6강의 배열처럼 번호(인덱스)로 한 글자씩 꺼낼 수 있습니다. 첫 글자가 0번이고, s.length는 글자 단위의 길이입니다. 그래서 마지막 글자는 s[s.length - 1]이고, 요즘은 s.at(-1)처럼 뒤에서부터 세는 방법도 씁니다. 없는 번호를 꺼내면 오류가 아니라 undefined가 나온다는 점도 배열과 같습니다.
배열과 크게 다른 점이 하나 있습니다. 문자열은 한번 만들면 안의 글자를 바꿀 수 없습니다. s[0] = "z"처럼 한 글자를 고치려 하면, 이 실습장처럼 엄격 모드(strict mode)로 도는 코드에서는 TypeError가 납니다. 그래서 이 강의의 모든 메서드는 원래 문자열을 고치지 않고 '새 문자열'을 돌려줍니다. 결과를 쓰려면 반드시 변수에 받아 두어야 합니다.
undefinedconst name = "민수";
const fruit = '사과';
const msg = `${name}님, ${fruit} 3개`;
console.log(msg);
console.log(msg.length);
console.log(msg[0], msg.at(-1), msg[99]);민수님, 사과 3개 10 민 개 undefined
let s = " hello ";
s.trim();
console.log("[" + s + "]");
s = s.trim();
console.log("[" + s + "]");[ hello ] [hello]
2.글자 수의 함정 — 한글과 이모지
length는 '눈에 보이는 글자 수'가 아니라 JavaScript가 문자열을 저장하는 단위(UTF-16 코드 단위)의 개수입니다. 다행히 우리가 쓰는 완성형 한글 한 글자는 한 단위라서 "한글".length는 2, "안녕하세요".length는 5로 보이는 대로 나옵니다. 영문·숫자·공백·문장부호도 한 글자가 한 단위입니다.
이모지는 다릅니다. 많은 이모지가 두 단위로 저장되어 "😀".length가 2입니다. 피부색이 붙은 손 모양처럼 여러 조각을 합쳐 한 모양으로 보이는 이모지는 4나 그 이상이 되기도 합니다. 문자열을 [...s]처럼 펼치면 코드 포인트 단위로 나뉘어 😀 하나는 1이 되지만, 합쳐진 이모지는 그래도 조각 수만큼 나옵니다. 화면에 보이는 모양 단위로 정확히 세려면 Intl.Segmenter 같은 별도 도구가 필요합니다.
한글도 늘 안심할 수는 없습니다. 같은 '한'이라도 초성·중성·종성을 따로 저장한 형태(자소 분리형, NFD)로 들어오면 길이가 3이 됩니다. 어떤 프로그램에서 복사한 파일 이름이 이런 형태로 들어오는 일이 있어서, 겉으로 같은 글자인데 === 비교가 false가 나오기도 합니다. 이럴 때 s.normalize("NFC")로 한 형태로 맞춘 뒤 비교합니다.
또 하나, 글자 수와 바이트 수는 다릅니다. 파일이나 네트워크에서 흔히 쓰는 UTF-8 방식으로는 완성형 한글 한 글자가 보통 3바이트입니다. '최대 90바이트'처럼 바이트로 제한하는 입력 칸이 있다면 length로 세면 안 됩니다. 정리하면, 한글 위주의 평범한 문장은 length로 세도 되지만, 이모지·자소 분리형 한글·바이트 제한이 섞이면 무엇을 세는지부터 정해야 합니다.
console.log("한글".length, "안녕하세요".length);
console.log("😀".length, [..."😀"].length);
console.log("👍🏽".length, [..."👍🏽"].length);2 5 2 1 4 2
const a = "한";
const b = a.normalize("NFD");
console.log(a.length, b.length);
console.log(a === b);
console.log(a === b.normalize("NFC"));
const bytes = new TextEncoder().encode("한글");
console.log(bytes.length);1 3 false true 6
| 문자열 | `length` | `[...s].length` | UTF-8 바이트 |
|---|---|---|---|
"한글" | 2 | 2 | 6 |
"abc" | 3 | 3 | 3 |
"😀" | 2 | 1 | 4 |
"👍🏽" | 4 | 2 | 8 |
3.자르기와 찾기 — `slice`, `indexOf`, `includes`
s.slice(시작, 끝)은 시작 번호부터 끝 번호 '바로 앞'까지를 잘라 새 문자열로 돌려줍니다. 끝을 빼면 마지막까지 자르고, 음수를 주면 뒤에서부터 셉니다. 그래서 s.slice(0, 3)은 앞 세 글자, s.slice(-4)는 뒤 네 글자입니다. 끝 번호가 포함되지 않는다는 규칙은 4강의 반복문에서 i < n으로 쓰던 것과 같은 감각이라, 길이는 '끝 − 시작'으로 바로 계산됩니다.
찾기에는 세 가지를 주로 씁니다. s.indexOf("찾을 말")은 처음 나오는 위치의 번호를, 없으면 -1을 돌려줍니다. 위치는 필요 없고 있는지만 알고 싶으면 s.includes("찾을 말")가 true/false를 돌려주어 3강의 조건문에 바로 넣기 좋습니다. startsWith와 endsWith는 앞이나 끝이 그 말로 시작·끝나는지 봅니다. 파일 이름이 ".csv"로 끝나는지 확인하는 식입니다.
indexOf의 결과를 조건에 그대로 쓰면 함정에 빠집니다. 찾는 말이 맨 앞에 있으면 결과가 0인데, 3강에서 본 것처럼 0은 falsy라서 if (s.indexOf("x"))는 '맨 앞에 있을 때' 거짓이 됩니다. 있는지만 볼 때는 includes를 쓰고, indexOf를 쓴다면 !== -1과 비교하세요. 또 이 메서드들은 대소문자를 구분하므로 "Apple".includes("apple")은 false입니다. 대소문자를 무시하려면 양쪽을 toLowerCase()로 맞춘 뒤 찾습니다.
const phone = "010-1234-5678";
console.log(phone.slice(0, 3));
console.log(phone.slice(-4));
console.log(phone.slice(4, 8));010 5678 1234
const file = "2026_판매기록.csv";
console.log(file.indexOf("_"));
console.log(file.indexOf("x"));
console.log(file.includes("판매"));
console.log(file.endsWith(".csv"));
console.log("Apple".includes("apple"));
const t = "Apple".toLowerCase();
console.log(t.includes("apple"));4 -1 true true false true
"[email protected]"에서 @ 앞의 아이디만 꺼내려면 어떻게 할까요?const email = "[email protected]";
const at = email.indexOf("@");
if (at === -1) {
console.log("이메일 형식이 아님");
} else {
console.log(email.slice(0, at));
}minsu
- 1단계
@의 위치를 찾습니다.indexOf("@")는 5입니다(m-i-n-s-u가 0~4번). - 2단계 0번부터 5번 '바로 앞'까지 자르면 됩니다.
slice(0, 5)이고, 길이는 5 − 0 = 5글자입니다. - 3단계
@가 없는 주소가 들어오면indexOf가-1이 되어slice(0, -1)은 마지막 한 글자를 뺀 엉뚱한 값이 됩니다. 그래서 먼저-1인지 확인하는 조건을 둡니다.
email.slice(0, email.indexOf("@")) — 다만 @가 없을 때를 먼저 걸러야 합니다.4.나누고 붙이고 다듬기 — `split`, `join`, `trim`
텍스트 처리의 절반은 split과 join입니다. s.split(",")는 쉼표를 기준으로 문자열을 잘라 배열로 돌려주고, arr.join("-")는 반대로 배열을 하나의 문자열로 이어 붙입니다. 문자열을 배열로 바꾸면 6강에서 배운 length·map·filter를 모두 쓸 수 있으므로 '나누기 → 배열로 처리 → 다시 붙이기'가 기본 흐름이 됩니다. 9강의 자동화 예제도 이 흐름 위에 서 있습니다.
split에는 알아 둘 모서리가 있습니다. 빈 문자열 ""을 나누면 빈 배열이 아니라 빈 문자열 하나가 든 배열 [ '' ]이 나옵니다. 쉼표가 두 번 이어지면 그 사이에 빈 문자열이 생깁니다. 그래서 '단어가 몇 개인가'를 split(" ").length로 세면 빈 입력이 1개로 나오거나 공백이 겹친 곳에서 수가 늘어납니다. 경계값을 꼭 함께 시험해야 하는 까닭입니다.
trim()은 앞뒤의 공백·탭·줄바꿈을 지웁니다(가운데 공백은 그대로). 사람이 입력한 값은 앞뒤에 공백이 붙어 오는 일이 많아서, 비교하거나 저장하기 전에 trim()을 거치는 습관이 좋습니다. toUpperCase()·toLowerCase()는 영문 대소문자를 바꾸고, 한글처럼 대소문자가 없는 글자는 그대로 둡니다. padStart(3, "0")은 길이가 3이 될 때까지 앞을 "0"으로 채워 "7"을 "007"로 만듭니다. 번호나 시각을 자릿수 맞춰 보여 줄 때 씁니다.
const line = "민수,사과,3";
const cols = line.split(",");
console.log(cols);
console.log(cols.length, cols[1]);
console.log(cols.join(" / "));[ '민수', '사과', '3' ] 3 사과 민수 / 사과 / 3
[ '' ]console.log("".split(","));
console.log("a,,b".split(","));
console.log(" Hi ".trim().toUpperCase());
console.log("7".padStart(3, "0"));
console.log("가나다".toUpperCase());[ '' ] [ 'a', '', 'b' ] HI 007 가나다
split("")(빈 문자열 기준)은 한 단위씩 쪼갭니다. 이모지가 섞이면 조각이 깨질 수 있으니 글자 단위로 나눌 때는[...s]가 더 안전합니다.split·trim·toUpperCase는 모두 새 값을 돌려줍니다. 원래 문자열은 그대로입니다.- 줄 단위로 나눌 때는
split("\n")을 씁니다. 윈도에서 만든 파일은 줄 끝에 보이지 않는 글자가 하나 더 붙어 있을 수 있어서, 나눈 뒤 각 줄을trim()하는 편이 안전합니다.
5.바꾸기 — `replace`와 `replaceAll`
s.replace("찾을 말", "바꿀 말")은 처음 나오는 하나만 바꿉니다. 전부 바꾸려면 replaceAll을 씁니다. 이 차이 때문에 '왜 첫 번째만 바뀌지?' 하는 일이 자주 생기므로, 바꾸는 코드를 짰으면 같은 말이 두 번 이상 나오는 입력으로 꼭 시험해 보세요. 두 메서드 모두 원래 문자열은 그대로 두고 바뀐 새 문자열을 돌려줍니다.
바꾸기는 '지우기'로도 씁니다. 바꿀 말을 빈 문자열 ""로 주면 찾은 부분이 사라집니다. 예를 들어 "010-1234-5678".replaceAll("-", "")는 "01012345678"입니다. 거꾸로 숫자만 있는 전화번호에 하이픈을 넣고 싶으면 slice로 잘라 join으로 붙이는 편이 깔끔합니다. 어떤 작업이든 원본은 그대로 두고 새 값을 만들기 때문에, 처리 전후를 나란히 놓고 비교하기 쉽습니다.
replace는 첫 번째 하나만const s = "사과-배-사과";
console.log(s.replace("사과", "귤"));
console.log(s.replaceAll("사과", "귤"));
console.log(s);귤-배-사과 귤-배-귤 사과-배-사과
const raw = "010-1234-5678";
const digits = raw.replaceAll("-", "");
console.log(digits);
const parts = [
digits.slice(0, 3),
digits.slice(3, 7),
digits.slice(7),
];
console.log(parts.join("-"));01012345678 010-1234-5678
6.정규식 맛보기 — 모양으로 찾기
지금까지는 '정확히 이 말'을 찾았습니다. '숫자가 이어진 부분', '공백이 여러 개 이어진 곳'처럼 모양으로 찾고 싶을 때 쓰는 것이 정규식(정규표현식)입니다. JavaScript에서는 / 두 개 사이에 패턴을 쓰고, 뒤에 붙는 g는 '처음 하나가 아니라 전부'라는 뜻입니다. 정규식은 그 자체로 작은 언어라 여기서는 자주 쓰는 몇 가지만 읽을 수 있을 정도로 봅니다.
\d는 숫자 한 글자, \s는 공백 한 글자(공백·탭·줄바꿈)이고, 뒤에 붙는 +는 '하나 이상 이어진'이라는 뜻입니다. 그래서 /\d+/g는 '이어진 숫자 덩어리 전부', /\s+/g는 '이어진 공백 전부'입니다. match는 찾은 덩어리를 배열로 돌려주고, 하나도 없으면 빈 배열이 아니라 null을 돌려준다는 점에 주의하세요. test는 찾았는지만 true/false로 알려 줍니다.
정규식은 강력하지만 읽기 어렵고, 조금만 틀려도 엉뚱한 곳이 맞거나 빠집니다. 원칙은 두 가지입니다. 먼저 includes·split·replaceAll로 되는 일은 그것으로 하고, 정규식이 꼭 필요할 때만 씁니다. 그리고 AI가 만들어 준 정규식이든 직접 쓴 것이든 맞아야 할 예와 맞으면 안 되는 예를 여러 개 넣어 정규표현식 테스터에서 확인한 뒤에 씁니다.
match는 못 찾으면 nullconst memo = "주문 3개, 반품 12개";
console.log(memo.match(/\d+/g));
console.log("숫자 없음".match(/\d+/g));
console.log(/\d/.test(memo));[ '3', '12' ] null true
const messy = " 사과 배 귤 ";
const clean = messy.trim().replace(/\s+/g, " ");
console.log("[" + clean + "]");
const masked = "010-1234-5678".replace(/\d/g, "*");
console.log(masked);[사과 배 귤] ***-****-****
| 조각 | 뜻 | 예 |
|---|---|---|
\d | 숫자 한 글자 | /\d+/g → 이어진 숫자 전부 |
\s | 공백 한 글자(탭·줄바꿈 포함) | /\s+/g → 이어진 공백 전부 |
+ | 앞의 것이 하나 이상 | /a+/ → a, aa, aaa |
^, $ | 문자열의 처음, 끝 | /^\d+$/ → 전부 숫자일 때만 |
g | 전부 찾기(붙이지 않으면 처음 하나) | s.replace(/\d/g, "*") |
📌 핵심 정리
- 문자열은 바꿀 수 없는 값이라 모든 메서드가 새 문자열을 돌려준다 — 결과를 변수에 받아야 한다
slice(시작, 끝)은 끝을 포함하지 않고,indexOf는 못 찾으면-1이다 — 있는지만 볼 때는includes- '나누기(
split) → 배열로 처리 → 붙이기(join)'가 텍스트 처리의 기본 흐름이고, 빈 문자열을 나누면[ '' ]이다 replace는 첫 번째 하나만,replaceAll은 전부 바꾼다length는 UTF-16 단위 수다 — 완성형 한글은 한 글자 1이지만 이모지는 2 이상, 자소 분리형 한글은 3, UTF-8 바이트 수는 또 다르다
🧪 코드 실습
텍스트를 자르고, 세고, 정리하는 함수를 직접 만들어 봅니다. 빈 문자열 같은 경계값 테스트도 함께 채점됩니다.
코드는 이 브라우저 안의 격리된 공간에서만 실행되고 서버로 보내지 않습니다. 네트워크에는 접속할 수 없고, 2초가 넘으면 멈춥니다. 고친 코드는 이 브라우저에만 저장됩니다. Ctrl+Enter(⌘+Enter)로 실행하고, Tab 키는 공백 두 칸입니다(Esc 다음 Tab 은 다음 칸으로 이동).
자바스크립트가 꺼져 있어 실행할 수는 없지만, 문제와 처음 코드, 자동 검사 내용, 예시 답안은 그대로 읽을 수 있습니다.
1이름 가리기
이름 name을 받아 첫 글자만 남기고 나머지 글자를 모두 *로 바꾼 문자열을 돌려주는 함수 maskName(name)을 만드세요. 예: "김민수" → "김**". 한 글자 이름은 그대로, 빈 문자열은 빈 문자열을 돌려줍니다.
maskName("김민수")기대값"김**"maskName("남궁지우")기대값"남***"maskName("이")기대값"이"maskName("")기대값""
💡 힌트
빈 문자열이면 name.length - 1이 -1이 되고, "*".repeat(-1)은 RangeError를 냅니다. Math.max(…, 0)이나 조건문으로 막으세요.
예시 답안 보기
function maskName(name) {
const first = name.slice(0, 1);
const rest = name.length - 1;
return first + "*".repeat(Math.max(rest, 0));
}2단어가 몇 번 나오나
문장 text에 단어 word가 몇 번 나오는지 세어 숫자로 돌려주는 함수 countWord(text, word)를 만드세요. 대소문자는 구분합니다. 예: countWord("사과 배 사과", "사과") → 2. text가 빈 문자열이면 0입니다. 다른 말 속에 들어 있어도 셉니다("사과나무" 속의 "사과"도 1번). word는 빈 문자열이 아니라고 가정합니다.
countWord("사과 배 사과", "사과")기대값2countWord("사과 배 사과", "귤")기대값0countWord("a-a-a-a", "a")기대값4countWord("Apple apple", "apple")기대값1countWord("", "사과")기대값0
💡 힌트
"a-a".split("a")는 [ '', '-', '' ]처럼 앞뒤에 빈 조각이 생겨도 개수는 '나온 횟수 + 1'입니다.
예시 답안 보기
function countWord(text, word) {
return text.split(word).length - 1;
}3공백 정리하기
문자열 s의 앞뒤 공백을 지우고, 가운데에 여러 개 이어진 공백은 공백 하나로 줄인 문자열을 돌려주는 함수 cleanSpaces(s)를 만드세요. 예: " 사과 배 " → "사과 배". 공백만 있는 문자열은 빈 문자열이 됩니다.
cleanSpaces(" 사과 배 ")기대값"사과 배"cleanSpaces("a b c")기대값"a b c"cleanSpaces("이미 깔끔함")기대값"이미 깔끔함"cleanSpaces(" ")기대값""cleanSpaces("")기대값""
💡 힌트
split(/\s+/)로 나눈 뒤 join(" ")으로 붙이는 방법도 됩니다. 단, 그때도 먼저 trim()해야 앞뒤에 빈 조각이 생기지 않습니다.
예시 답안 보기
function cleanSpaces(s) {
return s.trim().replace(/\s+/g, " ");
}4제목을 주소용 이름으로
영문 제목 title을 받아 주소에 쓰기 좋은 이름(slug)으로 바꾸는 함수 toSlug(title)를 만드세요. 규칙: 앞뒤 공백 제거 → 모두 소문자 → 이어진 공백은 하이픈 - 하나로. 예: " Hello World " → "hello-world".
toSlug(" Hello World ")기대값"hello-world"toSlug("My First Post")기대값"my-first-post"toSlug("JS")기대값"js"toSlug("a b c d")기대값"a-b-c-d"toSlug("")기대값""
💡 힌트
처음 코드의 replace(" ", "-")는 첫 공백 하나만 바꿉니다. 메서드는 점(.)으로 이어 부를 수 있고, 순서가 중요합니다.
예시 답안 보기
function toSlug(title) {
return title
.trim()
.toLowerCase()
.replace(/\s+/g, "-");
}🤖 AI에게 이렇게 물어보세요
복사해서 [ ] 부분을 내 상황으로 바꿔 쓰세요. 답을 그대로 믿지 말고 본문과 비교해 확인하세요.
텍스트 정리 코드를 만들고 싶을 때
JavaScript로 문자열을 정리하는 함수를 만들고 싶어. 입력 예: [입력 예 2~3개]. 원하는 결과: [각각의 기대 결과]. 정규식이 꼭 필요하지 않으면 `split`·`replaceAll`·`trim` 같은 기본 메서드로 써 주고, 빈 문자열과 공백만 있는 입력에서 어떻게 되는지도 알려 줘.
AI가 준 정규식이 무슨 뜻인지 모를 때
다음 정규식을 조각마다 나눠서 무슨 뜻인지 설명해 줘: [정규식]. 이 정규식에 맞는 예 3개와 맞을 것 같지만 맞지 않는 예 3개도 들어 줘. 내가 원하는 것은 [찾고 싶은 모양]인데 이 정규식이 그것과 다르게 동작하는 경우가 있으면 지적해 줘.
글자 수가 예상과 다르게 나올 때
JavaScript에서 [문자열 예]의 `length`가 [나온 값]으로 나와. 화면에 보이는 글자 수는 [보이는 수]야. 왜 다른지 UTF-16·코드 포인트·바이트 관점에서 설명하고, 내가 세야 하는 것이 [글자 수/바이트 수 중 무엇]일 때 쓸 수 있는 방법을 알려 줘.
🧰 관련 도구
이 강의에서 다룬 JSON·정규식·텍스트를 바로 확인하고 다듬어 볼 수 있는 도구입니다. 실제 개인정보나 비밀번호는 붙여 넣지 마세요.
- 정규표현식 테스터강의에서 맛본
\d+,\s+같은 패턴을 예문에 바로 대 보며, 맞아야 할 곳과 맞으면 안 될 곳을 눈으로 확인할 수 있습니다. - 글자수 세기글자 수(이모지도 1글자로 세는
[...s].length방식)와 UTF-8 바이트 수를 나란히 보여 주어, 이 강의에서 다룬 '무엇을 세느냐'의 차이를 실제 문장으로 비교해 볼 수 있습니다. - 텍스트 비교기
replaceAll이나 공백 정리를 하기 전과 후의 텍스트를 붙여 넣으면 바뀐 곳만 표시되어, 의도하지 않은 변경을 찾기 쉽습니다. - 대소문자 변환기
toUpperCase·toLowerCase가 하는 일을 도구로 먼저 보고, 같은 결과를 코드로 만들어 비교해 볼 수 있습니다.
- MDN Web Docs — String (JavaScript 레퍼런스)
- MDN Web Docs — 정규 표현식(Regular expressions) 안내서
- MDN Web Docs — String.prototype.normalize(), Intl.Segmenter
- ECMAScript 언어 명세(ECMA-262) — String 객체
학습 목표를 모두 이뤘다면 완료를 눌러 진도에 기록하세요.
이 브라우저에서는 저장할 수 없어 이 화면에서만 표시됩니다.