프로그래밍/JavaScript

[JavaScript Core] 문자열에 대해 자세히 알아보자

문_성 2023. 11. 14. 11:43
반응형


이번에는 자바스크립트의 기본 자료형 중 하나인 문자열에 대해 자세히 알아보도록 하겠습니다.

아마, 자바스크립트를 처음 배울 때 가장 먼저 접하는 문법 중 하나이며, 모르시는 분들이 없을 것이라고 생각됩니다.

따라서, 우리가 기본적으로 아는 문자열 그 자체가 아닌, 더욱 깊이 파고들어서 자세하게 알아보도록 합시다!



1. 문자열


문자열은 기본적으로 ''(작은따옴표) 혹은 ""(큰따옴표) 로 둘러쌓여집니다.

아래와 같이 말이죠.

let str1 = 'hello';
let str2 = "hello";


이것이 우리가 흔히 알고있는 문자열의 모습이자, 사용 방법입니다.

 

let str1 = `
    hello
    world
`;
let str2 = `str1: ${str1}`;


따옴표 말고도 ``(백틱) 을 둘러싸서 표현할 수도 있죠.

백틱으로 둘러싼 문자열은 줄바꿈을 할 수 있으며, ${변수이름} 을 통해 변수값을 대입할 수도 있습니다.

그럼, 따옴표로 감싸진 문자열은 줄바꿈을 하지 못할까요?

이를 위해서, 자바스크립트에는 특수 문자가 존재합니다.

"\n"; // 띄어쓰기
"\\"; // 역슬래시
"\'"; // 작은따옴표
"\""; // 큰따옴표
"\t"; // 탭
"\uXX"; // XX를 유니코드로 표현한 글자
"\uXXXX"; // XXXX를 유니코드로 표현한 글자
"\u{X ~ XXXXXX}"; // X ~ XXXXXX를 유니코드로 표현한 글자


위의 기호 말고도 다른 것들이 더 존재하지만, 이정도만 기억하셔도 충분하리라 생각됩니다.

let str1 = "hello";

console.log(str1.length); // 5
console.log(str1[0]); // h

str1[0] = "m"; // Error


단순 문자열을 선언하는 부분만 있는것이 아닌, 위처럼 문자열의 길이를 .length 프로퍼티로 접근할 수 있고, 대괄호 표기법([]) 을 통해 각각의 문자에도 접근할 수 있습니다.

그러나, 접근은 가능하지만 일반 객체나 배열처럼 각각의 문자를 수정하는 것은 불가능합니다. 이 점은 알아두시면 좋을 것 같습니다.



2. 문자열의 내장 메소드


문자열의 기본적인 부분과 기호들, 그리고 간단한 요소들에 대해 알아보았으니 이제는 내부적으로 지원하는 메소드들을 알아보도록 합시다.

 

 let str1 = "Hello, World!";
 
 console.log(str1.toUpperCase()); // HELLO, WORLD!
 console.log(str1.toLowerCase()); // hello, world!

 

먼저, toUpperCasetoLowerCase 메소드를 각각 알아보겠습니다.

 

toUpperCase 메소드는 함수명 그대로 모든 문자를 대문자로 만드는 메소드이고, toLowerCase 역시 함수명 그대로 모든 문자를 소문자로 만드는 메소드입니다.

 

간단하죠?

 

let str1 = "hello, world";
let str2 = "hello, hello";

console.log(str1.indexOf("hello")); // 0
console.log(str1.indexOf("Hello")); // -1

console.log(str2.indexOf("hello")); // 0
console.log(str2.indexOf("hello", 5)); // 7

 

다음으로는, 자바스크립트에서 엄청나게 쓰이는 메소드 중 하나인 indexOf 입니다.

 

indexOf 메소드는 첫번째 인자로 들어오는 문자열이 어느 위치에 있는지 알려주는 메소드이고, 해당하는 문자열이 있다면 그 문자열의 시작 인덱스를, 없다면 -1 을 반환합니다.

 

또한, 두번째 인자도 넘겨줄 수 있는데, 두번째 인자는 시작 위치의 인덱스를 나타내는 값으로, 위의 예제를 보면 "hello, hello".index("hello", 5) 라는 부분이 있죠?

 

이 부분은 인덱스가 5인 부분부터 그 후에 존재하는 "hello" 라는 문자열의 인덱스를 구하겠다는 의미입니다. 따라서, 결과가 0이 아닌 7이 되는 것이죠.

 

let str1 = "Hello, World!";

console.log(str1.includes("Hello")); // true
console.log(str1.includes("hello")); // false
console.log(str1.includes("Hello", 5)); // false

console.log(str1.startsWith("Hello")); // true
console.log(str1.startsWith("World")); // false

console.log(str1.endsWith("Hello")); // false
console.log(str1.endsWith("World!")); // true

 

다음으로, 각각 includes, startsWith, endsWith 메소드 입니다. 이 친구들도 복잡할 것 없이, 이름 그대로의 역할을 수행합니다.

 

includes 메소드는 인자로 들어오는 문자열이 대상에 존재하는지 유무를 boolean 으로 반환해주는 메소드이며, indexOf 과 마찬가지로 두번째 인자에 어디부터 탐색할 것인지 인덱스를 넘겨줄 수 있습니다.

 

startsWith 메소드와 endsWith 메소드는 이름 그대로 각각 인자로 들어오는 문자열로 시작하는지 혹은 끝나는지를 판단하는 메소드입니다.

 

let str1 = "Hello, World!";

console.log(str1.slice(1, 3)); // el
console.log(str1.slice(-4, -1)); // rld

console.log(str1.substring(1, 3)); // el
console.log(str1.substring(3, 1)); // el

console.log(str1.substr(1, 3)); // ell
console.log(str1.substr(-4, 2)); // rl

 

이번에는, 셋 다 비슷한 역할을 수행하는 각각 slice, substring, substr 메소드 입니다.

 

slice 메소드는 첫번째 인자와 두번째 인자 사이의 문자열을 잘라서 출력하는 메소드인데, 첫번째 인자는 포함하고 두번째 인자는 포함하지 않습니다. 그리고, 음수를 인자로 받을 수 있으며, 음수는 문자열의 끝에서부터 차례대로 -1, -2, -3 ... 으로 시작한다고 생각하시면 됩니다. (자르는 메커니즘은 동일합니다.)

 

substring 메소드는 slice 메소드와 거의 동일한데, .substring(3, 1) 과 같이 시작 인덱스와 끝 인덱스를 순서와 상관없이 받을 수 있으며, 음수를 받을 수 없어서 음수를 인자로 주면 0을 반환합니다.

 

substr 메소드는 첫번째 인자로부터 두번째 인자의 개수까지 문자열을 잘라서 출력하는 역할으로, 앞서 두 메소드랑 조금 다릅니다. 예로, . substr(1, 3) 은 인덱스가 1인 지점부터 3개의 문자열을 잘라서 결과로 가진다는 의미입니다. substr 메소드는 slice 메소드와 마찬가지로 첫번째 인자에 음수를 받을 수 있습니다. (두번째 인자는 개수를 의미하니 음수를 받을 수 없겠죠?)

 

이 셋 메소드는 혼동해서 쓰일 수도 있으며, 역할이 비슷하지만 조금씩 다르니 잘 기억하고 있어야 됩니다. 헷갈리더라도 꼭 구분해서 기억해주시길 바랍니다!

 

 

 

3. 문자열 심화

 

이번에는, 문자열에 대해 더욱 자세히 파고들어보겠습니다.

 

여기까지는 몰라도 크게 문제는 되지 않지만, 저는 개인적으로 깊이에 상관없이 기본적인 개념과 기초를 중요시하기 때문에, 당장 어딘가에 쓰이지는 않더라도 봐두셨으면 하는 마음입니다.

 

console.log("a" > "A"); // true
console.log('Österreich' > 'Zealand'); // true

 

일단 먼저, 문자열의 비교에 대해서 알아보겠습니다.

 

우리가 상식적으로 생각했을 때에는, 대문자가 소문자보다 크고, 알파벳 순서대로 크기가 정해질 것 같지만, 자바스크립트에서는 소문자가 무조건 대문자보다 크며, 발음 구별 기호(ex -> Ö) 가 들어간 문자는 비교에 있어서 알파벳 순서를 따르지 않습니다.

 

이러한 현상이 일어나는 이유는, 자바스크립트에서 문자열을 비교할 때, 유니코드의 값을 토대로 하기 때문입니다. 대문자의 유니코드는 소문자보다 무조건 작고, 발음 구별 기호가 포함된 문자들은 유니코드의 거의 끝자락에 위치하고 있기 때문이죠.

 

console.log("a".localeCompare("A")); // -1
console.log('Österreich'.localeCompare("Zealand")); // -1

 

따라서, 우리가 흔히 아는 상식으로 제대로 비교하기 위해서는 localeCompare 라는 내장 메소드를 이용해야 합니다. 

 

localeCompare 메소드는 인자로 들어온 문자열이 대상보다 작으면 -1, 같으면 0, 크면 1 을 반환해주며, 우리가 원하던 알파벳 순서와 소문자 < 대문자 대로 비교해줍니다.

 

어렵게 생각할 필요 없이, 앞으로 문자열을 비교할 때에는 단순 비교 연산자가 아닌, localeCompare 메소드를 이용하자는 것만 기억하시면 될 것 같습니다!

 

alert( '𝒳'.length ); // 2, 수학에서 쓰이는 대문자 X(그리스 문자 카이 - 옮긴이)
alert( '😂'.length ); // 2, 웃으면서 눈물 흘리는 얼굴을 나타내는 이모티콘
alert( '𩷶'.length ); // 2, 사용 빈도가 낮은 중국어(상형문자)

 

다음으로는, 문자들을 내부적으로 어떻게 표현하는지에 대해서 알아보도록 합시다.

 

보통, 일반적인 문자들은 2바이트(2 byte) 의 표현 체계를 사용합니다. 그러나, 2바이트 만으로는 모든 문자들을 표현할 수 없으니, 2바이트를 하나 더 추가하여 2바이트 쌍을 이용해 표현하기도 하는데, 이를 서로게이트 쌍(surrogate pair) 이라고 합니다. 

 

위의 코드에서 하나의 문자임에도 불구하고 길이가 2로 출력되는 이유가, 위의 문자들이 서로게이트 쌍으로 표현되고 있기 때문이죠.

 

alert('𝒳'.charCodeAt(0).toString(16)); // d835
alert('𝒳'.charCodeAt(1).toString(16)); // dcb3
alert("\ud835\udcb3"); // 𝒳

 

위와 같이, 서로게이트 쌍의 각각의 유니코드를 구한 후, "\ud835\udcb3" 와 같이 둘을 합쳐서 출력해보면 정상적으로 해당 문자가 나오는것을 볼 수 있습니다.

 

 

 

후....이렇게 문자열에 대해서 기초부터 심화까지 꼼꼼하게 알아보았는데, 양도 다른 글들에 비해서 상대적으로 많았고, 우리가 너무 쉽고 당연하게 사용하던 문자열이 이렇게까지 깊고 복잡할 수 있구나 하는 생각이 드는 파트였던 것 같습니다...ㅋㅋㅋ

 

따라서, 복습도 열심히 하셨으면 좋겠고, 다음에 올라올 글들도 많은 관심 부탁드립니다!

 

반응형