파트 1 - 4) 자료구조와 자료형 - 3) 문자열

문자형 파트 링크 : https://ko.javascript.info/string

자바스크립트에서 문자열은 페이지 인코딩 방식과 상관없이 항상 UTF-16 형식을 따른다.

따옴표

자바스크립트에서 따옴표의 종류 중 가장 눈에 띄는 것은 백틱(``)이다.

백틱으로는 우리가 자주 사용하는 ${value} 문법을 사용할 수 있다. (템플릿 리터럴 문법)

또 백틱은 템플릿 함수에서도 사용된다.함수명`인자` 이런 형식으로 작성하는 것이 작성법이다.

<script>
  const argValue1 = 'value1';
  const argValue2 = 'value2';

  const exam = (texts, ...values) => {
    console.log('texts==========>', texts);
    console.log('values=========>', values);
  }
  
  exam`예시 문구는 ${argValue1}${argValue2} 입니다.`; 
</script>

결과는 console 창에 ['예시 문구는', '과', '입니다.']['value1', 'value2'] 가 출력된다.

템플릿 함수의 첫번째 인자(texts)리터럴 문법으로 쓰인 문장 중 문자열을 배열 형태로 저장하고, 두번째 인자(values)리터럴 문법으로 쓰인 문장 중 ${value} 로 들어간 값을 배열 형태로 저장한다.

자주 사용되는 문법인 아니라고 한다. 처음 봐서 신기하다.


특수 기호

특수기호들은 복잡한 사용방법은 없다. 단지 문자열 사이에 저 특수기호들을 끼워 쓰면 특수기호가 가진 기능을 동작한다.
ex)

이런 특수문자들은 \ n 이렇게 두글자지만 특수문자 하나로 취급되어 문자열 길이에서 1을 차지한다. alert( `My\n`.length ); // 3

주의! length는 함수가 아니라 프로퍼티다!
str.length는 정상동작 하지만 str.length()는 에러가 발생한다...!
length는 함수가 아니라 숫자가 저장되는 프로퍼티다. 잊지말자!


특정 글자에 접근하기

문자열 내 특정 위치에 있는 글자에 접근하려면 [index] 같이 대괄호를 이용하거나 charAt(index) 메서드를 호출하면 된다.
(charAt메서드는 하위 호환성을 위해 남아있는 메서드고 요즘은 대괄홀 방식을 많이 이용한다.)

<script>
  let str = `Hello`;

  // 첫 번째 글자
  alert( str[0] ); // H
  alert( str.charAt(0) ); // H

  // 마지막 글자
  alert( str[str.length - 1] ); // o
</script>

두 방식의 차이는 반환할 글자가 없을 때 나타난다. 인자로 들어가는 index 자리에 글자가 없을 때 대괄호 방식은 undefined를 charAt은 빈 문자열을 반환한다.

<script>
  let str = `Hello`;

  alert( str[1000] ); // undefined
  alert( str.charAt(1000) ); // '' (빈 문자열)
</script>

for-of 구문을 사용하면 문자열을 구성하는 글자를 대상으로 반복 작업을 할 수 있다. (for-in과 헷갈리지 말자, for-in은 객체를 읽어오고 for-of는 배열, 문자열을 읽어온다!)

<script>
  for (let char of "Hello") {
    alert(char); // H,e,l,l,o (char는 순차적으로 H, e, l, l, o가 된다.)
  }
</script>

문자열의 불변성

문자열은 수정할 수 없다. 만약 문자열의 중간 글자 하나를 바꾸려고 하면 에러가 발생한다.
해결법은 새로운 문자열을 하나 만들어 이 문자열을 기존 문자열에 저장해주면 된다! (새 문자열 값을 기존 문자열에 저장하는 것)

<script>
  let str = 'Hi';

  str[0] = 'h'; // Error: Cannot assign to read only property '0' of string 'Hi'
  alert( str[0] ); // 동작하지 않는다.
  
  //============새 문자열을 기존 문자열에 할당============
  
  let str = 'Hi';

  str = 'h' + str[1]; // 문자열 전체를 교체

  alert( str ); // hi
</script>

부분 문자열 찾기

  • str.indexOf
    str.indexOf('찾을 문자', number);
    n번째(number)에 있는 문자('찾을 문자')를 찾아주는 메서드다.
<script>
  let str = 'Widget with id';

  alert( str.indexOf('id', 2) )
  // 12, 두번째로 등장하는 'id'는 인덱스 12번에 있음
</script>

문자열 전체를 대상으로 값을 변화시키고 싶다면 반복문 안에 indexOf를 사용하면 된다.

<script>
  let str = 'As sly as a fox, as strong as an ox';

  let target = 'as'; 

  let pos = 0; //1로 해도 상관은 없다
  
  while (true) {
    let foundPos = str.indexOf(target, pos); //index를 반환
    if (foundPos == -1) break;

    alert( `위치: ${foundPos}` ); // 7, 17, 27
    pos = foundPos + 1; // 다음 위치를 기준으로 검색을 이어감.
  }
</script>

좀 더 간단하게 쓰면 아래와 같이 쓸 수 있다.

<script>
  let str = "As sly as a fox, as strong as an ox";
  let target = "as";

  let pos = -1;
  while ((pos = str.indexOf(target, pos + 1)) != -1) {
    alert( `위치: ${pos}` );
  }
</script>

* str.lastIndexOf(substr, position) 함수
str.lastIndexOf(substr, position)는 indexOf와 비슷한 기능을 하는데 문자열 끝에서부터 부분 문자열을 찾는다.
반환되는 부분 문자열 위치는 문자열 끝이 기준이다.

주의 if문에서 indexOf를 쓰려면 주의해야한다.

<script>
  let str = "Widget with id";

  if (str.indexOf("Widget")) {
      alert("찾았다!"); // 의도한 대로 동작하지 않는다.
  }
</script>

위 예시를 실행하면 아무런 동작도 일어나지 않습니다.
str.indexOf("Widget");은 위 예시에서 Widget이 있는 인덱스인 0을 반환하는데 if문에서 0은 false로 간주되기 때문에 if문의 동작부인 alert 함수가 동작하지 않습니다.


비트 NOT 연산자(~)를 사용한 기법

비트 NOT 연산자는 피연자를 소수부는 모두 버린 정수로 바꾼 후(+1) 모든 비트를 반전(-)한다.

예를들어 n이 32비트 정수일 때 ~n은 -(n+1)이 된다.

<script>
  alert( ~2 ); // -3, -(2+1)과 같음
  alert( ~1 ); // -2, -(1+1)과 같음
  alert( ~0 ); // -1, -(0+1)과 같음
  alert( ~-1 ); // 0, -(-1+1)과 같음
</script>

위 예시에서 본 바와 같이 부호가 있는 32비트 정수 n 중, ~n을 0으로 만드는 경우는 n == -1일 때가 유일하다.

이 비트 NOT 연산자를 이용하면 조건문의 조건부를 줄일 수 있다.
예를 들어 indexOf가 -1을 반환하는 경우를 찾을 경우 ~str.indexOf("...")를 이용할 수 있다.

<script>
  let str = "Widget";

  if (~str.indexOf("Widget")) {
    alert( '찾았다!' ); // 의도한 대로 동작한다.
  }
  //str.indexOf("Widget")은 0을 반환하고 ~0은 -1이니까!
</script>

사실 이런 코드는 직관적이지 않아 추천하지 않는 방식이다.
하지만 오래된 스크립트에서 쉽게 만날 수 있기 때문에 알아두는 건 필요하다.

if (~str.indexOf(...)) 패턴의 코드는 '부분 문자열인지 확인'하는 코드라고 기억하면 된다.

참고로 -1 이외에도 ~ 연산자 적용 시 0을 반환하는 숫자는 다양하다.
아주 큰 숫자에 ~ 연산자를 적용하면 32비트 정수로 바꾸는 과정에서 잘림 현상이 발생하기 때문이다. 이런 숫자 중 가장 큰 숫자는 4294967295이다. (~4294967295는 0임).
문자열이 아주 길지 않은 경우에만 ~ 연산자가 의도한 대로 작동한다는 것도 알고 있어야한다.

현재는 .includes 메서드를 사용해 부분 문자열 포함 여부를 확인 하는 방법을 주로 사용한다.


includes, startsWith, endsWith

str.includes(substr, pos)는 str에 부분 문자열 substr이 있는지에 따라 true나 false를 반환하는 메서드다.

부분 문자열의 위치 정보는 필요하지 않고 포함 여부만 알고 싶을 때 적합한 메서드다.

<script>
  alert( "Widget with id".includes("Widget") ); // true

  alert( "Hello".includes("Bye") ); // false
</script>

.includes 메서드도 indexOf 메서드와 마찬가지로 두번째 인수를 넘기면 해당 위치(두번째 인수가 가리키는 위치) 부분 문자열을 검색한다.

<script>
  alert( "Widget".includes("id") ); // true
  alert( "Widget".includes("id", 3) ); // false, 세 번째 위치 이후엔 "id"가 없다. (2를 넣어도 마찬가지)
</script>

str.startsWith와 str.endsWith 메서드는 이름 그대로 str이 특정 문자열로 시작하는지(start with) 여부특정 문자열로 끝나는지(end with) 여부를 확인할 때 사용한다.

<script>
  alert( "Widget".startsWith("Wid") ); // true, "Widget"은 "Wid"로 시작.
  alert( "Widget".endsWith("get") ); // true, "Widget"은 "get"으로 끝남.
</script>

부분 문자열 추출하기

자바스크립트에서 부분 문자열 추출과 관련된 메서드는 substring, substr, slice 세가지가 있다.

1) str.slice(start [, end])
문자열의 start부터 end까지(end 미포함)를 반환한다.

<script>
  let str = "stringify";
  alert( str.slice(0, 5) ); // 'strin', 0번째부터 5번째 위치까지(5번째 위치의 글자는 포함하지 않음)
  alert( str.slice(0, 1) ); // 's', 0번째부터 1번째 위치까지(1번째 위치의 자는 포함하지 않음)
</script>

만약 두번째 인수를 주지 않으면 할당한 인수부터 문자열 끝까지를 반환한다.

<script>
  let str = "stringify";
  alert( str.slice(2) ); // ringify, 인덱스 2번부터 끝까지
</script>

start 인수와 end 인수에는 음수도 들어갈 수 있다. 인수가 음수면 문자열 뒤에서부터 탐색한다.

<script>
  let str = "stringify";

  // 끝에서 4번째부터 시작해 끝에서 1번째 위치까지
  alert( str.slice(-4, -1) ); // gif
</script>

2) str.substring(start [, end])
start와 end 사이에 있는 문자열을 반환한다.
substring은 slice와 아주 유사하지만 start가 end보다 커도 괜찮다는 데 차이가 있다. start가 크든 end가 크든 인수1~인수2 혹은 인수2~인수1을 범위로 사용하기 때문이다!

<script>
  let str = "stringify";

  // 동일한 부분 문자열을 반환
  alert( str.substring(2, 6) ); // "ring"
  alert( str.substring(6, 2) ); // "ring"

  // slice를 사용하면 start가 end보다 클 경우 빈 문자열을 반환한다.
  alert( str.slice(2, 6) ); // "ring" (같음)
  alert( str.slice(6, 2) ); // "" (빈 문자열)
</script>

substring은 음수 인수를 허용하지 않는다! 음수는 0으로 처리된다.

3) str.substr(start [, length])
start에서부터 length개의 글자를 반환한다.
substr은 end의 위치 대신 길이를 기준으로 문자열을 추출한다.
마찬가지로 첫번째 인수가 음수면 문자열 뒤에서부터 개수를 센다.

<script>
  let str = "stringify";
  alert( str.substr(2, 4) ); // ring, 두 번째부터 글자 네 개
  
  let str = "stringify";
alert( str.substr(-4, 2) ); // gi, 끝에서 네 번째 위치부터 글자 두 개
</script>

문자열 비교하기

문자열을 비교연산자로 비교할 때 특이한 결과가 나오는 경우가 있다.
1) 소문자는 항상 대문자보다 크다.
2) 발음 구별기호(diacritical mark)가 붙은 문자는 알파벳 순서 기준을 따르지 않는다.

<script>
alert( 'a' > 'Z' ); // true

alert( 'Österreich' > 'Zealand' ); // true (Österreich는 오스트리아를 독일어로 표기한 것)
</script>

자바스크립트는 모든 글자가 숫자 형식의 코드와 매칭되는 UTF-16을 이용해 문자열을 인코딩한다.
코드로 글자를 얻거나 글자에서 연관 코드를 알아낼 수 있는 메서드들이 있다.

1) str.codePointAt(pos)
pos에 위치한 글자의 코드를 반환한다.

<script>
// 글자는 같지만 케이스는 다르므로 반환되는 코드가 다르다.
// 케이스가 뭘 의미하는 건지?? 호출부가 다르다는 의미로 추정된다.
alert( "z".codePointAt(0) ); // 122
alert( "Z".codePointAt(0) ); // 90
</script>

2) String.fromCodePoint(code)
숫자 형식의 code에 대응하는 글자를 만들어준다.
alert( String.fromCodePoint(90) ); // Z

\u와 16진수 코드를 이용하는 방식도 있다.

<script>
  // 90을 16진수로 변환하면 5a
  alert( '\u005a' ); // Z
</script>

코드 65~220 사이에는 라틴계열 알파벳, 기타 글자들이 포함 돼있다.

<script>
  let str = '';

  for (let i = 65; i <= 220; i++) {
    str += String.fromCodePoint(i);
  }
  alert( str );
  // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„
  // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜ
</script>

위 예시를 보면 대문자 알파벳=>특수문자=>소문자 알파벳=>거의 마지막에 강세가 들어간(?) 알파벳이 나오는 것을 확인할 수 있다.

글자는 글자에 대응하는 숫자 형식의 코드를 기준으로 비교된다.
코드가 크면 대응하는 글자 역시 크다고 취급된다. (a(코드:97) > Z(코드:90))
알파벳 소문자의 코드가 대문자의 코드보다 크므로 소문자가 대문자 뒤에 온다.
Ö 같은 글자는 일반 알파벳과 멀리 떨어져 있다. Ö의 코드는 알파벳 소문자의 코드보다 훨씬 큰 코드를 가진다.


문자열 제대로 비교하기

문자열을 비교하려면 일단 페이지에서 어떤 언어를 사용하고 있는지 브라우저가 알고 있어야 한다.
최근의 브라우저들은 국제화 관련 표준인 ECMA-402를 지원한다. (IE10은 Intl.js 라이브러리를 사용해야 한다.)
ECMA-402는 언어가 다를 때 적용할 수 있는 문자열 비교 규칙과 이를 준수하는 메서드가 정의되어 있다.

str.localeCompare(str2)를 호출하면 ECMA-402의 규칙에 따라 str이 str보다 작은지, 같은지, 큰지를 나타내는 정수가 반환된다.

str < str2 : 음수를 반환
str > str2 : 양수를 반환
str == str2 : 0을 반환

예시) alert( 'Österreich'.localeCompare('Zealand') ); // -1


위에서 설명한 메서드들 외에도 str.trim, str.repeate와 같은 유용한 메서드들도 있다.

좋은 웹페이지 즐겨찾기