5. 자료구조와 자료형(1)

protect-me·2021년 6월 1일
0
post-thumbnail

5.1 원시값의 메서드


  • 자바스크립트는 원시값(문자열, 숫자 등)을 마치 객체처럼 다룰 수 있게 해주기 때문에 객체에서처럼 메서드를 호출할 수 있음
  • 하지만 원시값이 객체는 아니라는 점을 상기
  • 원시값 : 원시형 값.
    원시형의 종류는 문자(string), 숫자(number), bigint, 불린(boolean), 심볼(symbol), null, undefined형으로 총 일곱 가지
  • 객체 : 프로퍼티에 다양한 종류의 값을 저장 가능
    {name : "John", age : 30}와 같이 대괄호 {}를 사용해 만들 수 있습니다. 자바스크립트에는 여러 종류의 객체가 있는데, 함수도 객체의 일종임
  • 객체의 장점 중 하나는 함수를 프로퍼티로 저장할 수 있다는 것

원시값을 객체처럼 사용하기

모순적인 상황

  • 문자열이나 숫자와 같은 원시값을 다루어야 하는 작업이 많은데, 메서드를 사용하면 작업을 수월하게 할 수 있을 것 같다는 생각이 듭니다.
  • 그런데 원시값은 가능한 한 빠르고 가벼워야 합니다.

해결책 모색

  1. 원시값은 원시값 그대로 남겨둬 단일 값 형태를 유지합니다.
  2. 문자열, 숫자, 불린, 심볼의 메서드와 프로퍼티에 접근할 수 있도록 언어 차원에서 허용합니다.
  3. 이를 가능하게 하기 위해, 원시값이 메서드나 프로퍼티에 접근하려 하면 추가 기능을 제공해주는 특수한 객체, "원시 래퍼 객체(object wrapper)"를 만들어 줍니다. 이 객체는 곧 삭제됩니다.
  • 예시 : 인수로 받은 문자열의 모든 글자를 대문자로 바꿔주는 메서드 str.toUpperCase()
let str = "Hello";
alert( str.toUpperCase() ); // HELLO
  1. 문자열 str은 원시값이므로 원시값의 프로퍼티(toUpperCase)에 접근하는 순간 특별한 객체가 만들어집니다. 이 객체는 문자열의 값을 알고 있고, toUpperCase()와 같은 유용한 메서드를 가지고 있습니다.
  2. 메서드가 실행되고, 새로운 문자열이 반환됩니다(alert 창에 이 문자열이 출력됩니다).
  3. 특별한 객체는 파괴되고, 원시값 str만 남습니다.
  • 위와 같은 내부 프로세스를 통해 원시값을 가볍게 유지하면서 메서드를 호출할 수 있음
  • 자바스크립트 엔진은 내부 최적화가 잘 되어있어 메서드를 호출해도 많은 리소스를 쓰지 않음

String/Number/Boolean를 생성자론 쓰지 맙시다.
몇몇 상황에서 혼동을 불러일으킴

null/undefined는 메서드가 없습니다.
특수 자료형인 nullundefined의 원시값(null/undefined)은 위와 같은 법칙을 따르지 않습니다. 이 자료형과 연관되는 "래퍼 객체"도 없고, 메서드도 제공하지 않습니다. 어떤 의미에서는 두 자료형이 "가장 원시적"이라 할 수 있음



5.2 숫자형


요약
0이 많이 붙은 큰 숫자는 다음과 같은 방법을 사용해 씁니다.

  • 0의 개수를 'e' 뒤에 추가합니다. 123e6은 0이 6개인 숫자, 123000000을 나타냅니다.
  • 'e' 다음에 음수가 오면, 음수의 절댓값 만큼 10을 거듭제곱한 숫자로 주어진 숫자를 나눕니다. 123e-6은 0.000123을 나타냅니다.

다양한 진법을 사용할 수도 있습니다.

  • 자바스크립트는 특별한 변환 없이 16진수(0x), 8진수(0o), 2진수(0b)를 바로 사용할 수 있게 지원합니다.
  • parseInt(str, base)를 사용하면 str을 base진수로 바꿔줍니다(단, 2 ≤ base ≤ 36).
  • num.toString(base)는 숫자를 base진수로 바꾸고, 이를 문자열 형태로 반환합니다.

12pt나 100px과 같은 값을 숫자로 변환하는 것도 가능합니다.

  • parseInt/parseFloat를 사용하면 문자열에서 숫자만 읽고, 읽은 숫자를 에러가 발생하기 전에 반환해주는 ‘약한’ 형 변환을 사용할 수 있습니다.

소수를 처리하는 데 쓰이는 메서드는 다음과 같습니다.

  • Math.floor, Math.ceil, Math.trunc, Math.round, num.toFixed(precision)를 사용하면 어림수를 구할 수 있습니다.
  • 소수를 다룰 땐 정밀도 손실에 주의하세요.

숫자를 입력하는 다양한 방법

  • 'e'는 e 왼쪽의 수에 e 오른쪽에 있는 수만큼의 10의 거듭제곱을 곱하는 효과
let billion = 1e9;  // 10억, 1과 9개의 0
alert( 7.3e9 );  // 73억 (7,300,000,000)

let ms = 1e-6; // 1에서 왼쪽으로 6번 소수점 이동

16진수, 2진수, 8진수

// 16진수
alert( 0xff ); // 255
alert( 0xFF ); // 255 (대·소문자를 가리지 않으므로 둘 다 같은 값을 나타냅니다.)
// 2진수
let a = 0b11111111; // 255의 2진수
// 8진수
let b = 0o377; // 255의 8진수

toString(base)

  • num.toString(base) 메서드는 base진법으로 num을 표현한 후, 이를 문자형으로 변환해 반환함
  • base2~36, 기본값은 10
let num = 255;
alert( num.toString(16) );  // ff
alert( num.toString(2) );   // 11111111

점 두 개와 메서드 호출
123456..toString(36)
(123456).toString(36)

어림수 구하기

  • Math.floor
    소수점 첫째 자리에서 내림(버림). 3.1은 3, -1.1은 -2
  • Math.ceil
    소수점 첫째 자리에서 올림. 3.1은 4, -1.1은 -1
  • Math.round
    소수점 첫째 자리에서 반올림. 3.1은 3, 3.6은 4, -1.1은 -1
  • Math.trunc (IE에서 지원하지 않음)
    소수부를 무시. 3.1은 3이 되고 -1.1은 -1

소수점 n-th번째 수를 기준으로 어림수를 구할 때

  1. 곱하기와 나누기
    소수점 두 번째 자리 숫자까지만 남기고 싶은 경우, 숫자에 100 또는 100보다 큰 10의 거듭제곱 수를 곱한 후, 원하는 어림수 내장 함수를 호출하고 처음 곱한 수를 다시 나누면 됩니다.
let num = 1.23456;
alert( Math.floor(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
  1. toFixed(n)
    소수점 n 번째 수까지의 어림수를 구한 후 이를 문자형으로 반환해주는 메서드
    Math.round와 유사하게 가장 가까운 값으로 올림 혹은 버림)
    toFixedd의 반환값은 문자열이기 때문에 소수부의 길이가 인수보다 작으면 끝에 0이 추가됨
let num = 12.36;
alert( num.toFixed(1) ); // "12.4"

부정확한 계산

  • 숫자는 내부적으로 64비트 형식 IEEE-754으로 표현되기 때문에 숫자를 저장하려면 정확히 64비트가 필요
  • 64비트 중 52비트는 숫자를 저장하는 데 사용되고, 11비트는 소수점 위치를(정수는 0), 1비트는 부호를 저장하는 데 사용
  • 그런데 숫자가 너무 커지면 64비트 공간이 넘쳐서 Infinity로 처리됩니다.
alert( 0.1 + 0.2 ); // 0.30000000000000004
  • 10의 거듭제곱으로 나눈 값은 10진법에서 잘 동작하지만 3으로 나누게 되면 10진법에서 제대로 동작하지 않음
  • 같은 이유로 2진법 체계에서 2의 거듭제곱으로 나눈 값은 잘 동작하지만 1/10같이 2의 거듭제곱이 아닌 값으로 나누게 되면 무한 소수가 됨
  • 10진법에서 1/3을 정확히 나타낼 수 없듯이, 2진법을 사용해 0.1 또는 0.2를 정확하게 저장하는 방법은 없음
  • 해결 방법
let sum = 0.1 + 0.2;
alert( +sum.toFixed(2) ); // 0.3

isNaN과 isFinite

  • Infinity-Infinity – 그 어떤 숫자보다 큰 혹은 작은 특수 숫자 값
  • NaN – 에러를 나타내는 값
  • isNaN(value) – 인수를 숫자로 변환한 다음 NaN인지 테스트함
  • 🚨 NaN은 NaN 자기 자신을 포함하여 그 어떤 값과도 같지 않음 alert( NaN === NaN ); // false
  • isFinite(value) – 인수를 숫자로 변환하고 변환한 숫자가 NaN/Infinity/-Infinity가 아닌 일반 숫자인 경우 true를 반환함
  • 🚨 빈 문자열이나 공백만 있는 문자열은 isFinite를 포함한 모든 숫자 관련 내장 함수에서 0으로 취급됨

Object.is와 비교하기

Object.is는 ===처럼 값을 비교할 때 사용되는 특별한 내장 메서드인데, 아래와 같은 두 가지 에지 케이스에선 ===보다 좀 더 신뢰할만한 결과를 보여줍니다.

  1. NaN을 대상으로 비교할 때: Object.is(NaN, NaN) === true
  2. 0과 -0이 다르게 취급되어야 할 때: Object.is(0, -0) === false
    숫자를 나타내는 비트가 모두 0이더라도 부호를 나타내는 비트는 다르므로 0과 -0은 사실 다른 값이긴 합니다.
    이 두 에지 케이스를 제외하곤, Object.is(a, b)a === b의 결과는 같습니다.

이런 식의 비교는 자바스크립트 명세서에서 종종 찾아볼 수 있습니다. 내부 알고리즘에서 두 값을 비교해야 하는데, 비교 결과가 정확해야 하는 경우 Object.is를 사용하죠.

parseInt와 parseFloat

  • 단항 덧셈 연산자 + 또는 Number()를 사용하여 숫자형으로 변형할 때, 피연산자가 숫자가 아니면 형 변환이 실패
    alert( +"100px" ); // NaN
  • parseIntparseFloat 두 함수는 불가능할 때까지 문자열에서 숫자를 읽음
  • 숫자를 읽는 도중 오류가 발생하면 이미 수집된 숫자를 반환
  • parseInt는 정수, parseFloat는 부동 소수점 숫자를 반환
alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5

alert( parseInt('12.3') ); // 12, 정수 부분만 반환됩니다.
alert( parseFloat('12.3.4') ); // 12.3, 두 번째 점에서 숫자 읽기를 멈춥니다.

parseInt(str, radix)의 두 번째 인수
radix는 원하는 진수를 지정해 줄 때 사용

기타 수학 함수

  • Math.random()
    0과 1 사이의 난수를 반환(1은 제외).
  • Math.max(a, b, c...) / Math.min(a, b, c...)
    인수 중 최대/최솟값을 반환
  • Math.pow(n, power)
    n을 power번 거듭제곱한 값을 반환합니다.


5.3 문자열


  • 자바스크립트엔 세 종류의 따옴표가 있는데, 이 중 하나인 백틱은 문자열을 여러 줄에 걸쳐 쓸 수 있게 해주고 문자열 중간에 ${…}을 사용해 표현식도 넣을 수 있다는 점이 특징입니다.
  • 자바스크립트에선 UTF-16을 사용해 문자열을 인코딩합니다.
  • \n 같은 특수 문자를 사용할 수 있습니다. \u...를 사용하면 해당 문자의 유니코드를 사용해 글자를 만들 수 있습니다.
  • 문자열 내의 글자 하나를 얻으려면 대괄호 [index]를 사용하세요.
  • 부분 문자열을 얻으려면 slice나 substring을 사용하세요.
  • 소문자로 바꾸려면 toLowerCase, 대문자로 바꾸려면 toUpperCase를 사용하세요.
  • indexOf를 사용하면 부분 문자열의 위치를 얻을 수 있습니다. 부분 문자열 여부만 알고 싶다면 includes/startsWith/endsWith를 사용하면 됩니다.
  • 특정 언어에 적합한 비교 기준 사용해 문자열을 비교하려면 localeCompare를 사용하세요. 이 메서드를 사용하지 않으면 글자 코드를 기준으로 문자열이 비교됩니다.

이외에도 문자열에 쓸 수 있는 유용한 메서드 몇 가지가 있습니다.

  • str.trim() – 문자열 앞과 끝의 공백 문자를 다듬어 줍니다(제거함).
  • str.repeat(n) – 문자열을 n번 반복합니다.
  • 자바스크립트엔 글자 하나만 저장할 수 있는 별도의 자료형이 없어 텍스트 형식의 데이터는 길이에 상관없이 문자열 형태로 저장됨
  • 자바스크립트에서 문자열은 페이지 인코딩 방식과 상관없이 항상 UTF-16 형식을 따름

따옴표

  • 템플릿 리터럴(template literal) : 표현식을 ${…}로 감싸고 이를 백틱으로 감싼 문자열 중간에 넣어주면 해당 표현식을 문자열 중간에 쉽게 삽입할 수 있음
function sum(a, b) {
  return a + b;
}

alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3.
  • 백틱을 사용하면 문자열을 여러 줄에 걸쳐 작성할 수도 있음
let guestList = `손님:
 * John
 * Pete
 * Mary
`;
alert(guestList); // 리스트를 여러 줄에 걸쳐 작성

특수 기호

특수 문자설명
\n줄 바꿈
\', \"따옴표
\역슬래시
\t
alert( 'I\'m the Walrus!' ); // I'm the Walrus!
// 백틱을 사용하면 좀 더 우아하게 표현 가능
alert( `I'm the Walrus!` ); // I'm the Walrus!

문자열의 길이

  • length 프로퍼티엔 문자열의 길이가 저장됨

length는 프로퍼티입니다.
자바스크립트 이외의 언어를 사용했던 개발자들은 str.length가 아닌 str.length()로 문자열의 길이를 알아내려고 하는 경우가 있습니다. 하지만 원하는 대로 동작하지 않습니다.
length는 함수가 아니고, 숫자가 저장되는 프로퍼티라는 점에 주의하시기 바랍니다. 뒤에 괄호를 붙일 필요가 없습니다.

특정 글자에 접근하기

let str = `Hello`;

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

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

// 접근하려는 위치에 글자가 없는 경우
alert( str[1000] ); // undefined
alert( str.charAt(1000) ); // '' (빈 문자열)
  • for..of를 사용하면 문자열을 구성하는 글자를 대상으로 반복 작업 가능
for (let char of "Hello") {
  alert(char); // H,e,l,l,o (char는 순차적으로 H, e, l, l, o가 됨)
}

문자열의 불변성

  • 문자열은 수정할 수 없기 때문에 문자열의 중간 글자 하나를 바꾸려고 하면 에러가 발생
  • 이런 문제를 피하려면 완전히 새로운 문자열을 하나 만든 다음, 이 문자열을 str에 할당

대·소문자 변경하기

  • 메서드 toLowerCase()toUpperCase()는 대문자를 소문자로, 소문자를 대문자로 변경(케이스 변경)시킴
alert( 'Interface'.toUpperCase() ); // INTERFACE
alert( 'Interface'.toLowerCase() ); // interface

부분 문자열 찾기

str.indexOf

  • str.indexOf(substr, pos)
  • 문자열 strpos에서부터 시작해, 부분 문자열 substr이 어디에 위치하는지를 찾아줌
  • 원하는 부분 문자열을 찾으면 위치를 반환하고 그렇지 않으면 -1을 반환
let str = 'Widget with id';

alert( str.indexOf('Widget') ); // 0, str은 'Widget'으로 시작함
alert( str.indexOf('widget') ); // -1, indexOf는 대·소문자를 따지므로 원하는 문자열을 찾지 못함

alert( str.indexOf("id") ); // 1, "id"는 첫 번째 위치에서 발견됨 (Widget에서 id)
  • str.indexOf(substr, pos)의 두 번째 매개변수 pos는 선택적으로 사용할 수 있는데, 이를 명시하면 검색이 해당 위치부터 시작

  • 문자열 내 부분 문자열 전체를 대상으로 무언가를 하고 싶다면 반복문 안에 indexOf를 사용하면 됩니다. 반복문이 하나씩 돌 때마다 검색 시작 위치가 갱신되면서 indexOf가 새롭게 호출됨

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

let target = 'as'; // as를 찾아봅시다.

let pos = 0;
while (true) {
  let foundPos = str.indexOf(target, pos);
  if (foundPos == -1) break;

  alert( `위치: ${foundPos}` );
  pos = foundPos + 1; // 다음 위치를 기준으로 검색을 이어갑니다.
}
  • 리팩토링
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}` );
}

str.lastIndexOf(substr, position)
str.lastIndexOf(substr, position)는 indexOf와 유사한 기능을 하는 메서드입니다. 문자열 끝에서부터 부분 문자열을 찾는다는 점만 다릅니다.

  • 🚨 if문의 조건식에 indexOf를 쓸 때 아래와 같이 코드들 작성하면 원하는 결과를 얻을 수 없음
let str = "Widget with id";

if (str.indexOf("Widget")) {
    alert("찾았다!"); // 의도한 대로 동작하지 않습니다.
}
  • str.indexOf("Widget")은 0을 반환하는데, if문에선 0false로 간주하므로 alert 창이 뜨지 않음
  • 🚨 따라서 부분 문자열 여부를 검사하려면 아래와 같이 -1과 비교해야 함
let str = "Widget with id";

if (str.indexOf("Widget") != -1) {
    alert("찾았다!"); // 의도한 대로 동작합니다.
}

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

  • 비트(bitwise) NOT 연산자 ~를 사용한 기법
  • 비트 NOT 연산자는 피연산자를 32비트 정수로 바꾼 후(소수부는 모두 버려짐) 모든 비트를 반전함
  • 따라서 n이 32비트 정수일 때 ~n-(n+1)
  • 부호가 있는 32비트 정수 n 중, ~n을 0으로 만드는 경우는 n == -1일 때가 유일함
  • 이를 응용해서 indexOf가 -1을 반환하지 않는 경우를 if ( ~str.indexOf("...") )로 검사하면 아래와 같이 리팩토링 가능
let str = "Widget";

if (~str.indexOf("Widget")) {
  alert( '찾았다!' ); // 의도한 대로 동작합니다.
}

includes, startsWith, endsWith

  • str.includes(substr, pos)str에 부분 문자열 substr이 있는지에 따라 truefalse를 반환함
  • 부분 문자열의 위치 정보는 필요하지 않고 포함 여부만 알고 싶을 때 적합한 메서드
  • 메서드 str.startsWithstr.endsWith는 메서드 이름 그대로 문자열 str이 특정 문자열로 시작하는지(start with) 여부와 특정 문자열로 끝나는지(end with) 여부를 확인할 때 사용 가능
alert( "Widget with id".includes("Widget") ); // true
alert( "Hello".includes("Bye") ); // false
alert( "Widget".includes("id") ); // true

alert( "Widget".includes("id", 3) ); // false, 세 번째 위치 이후엔 "id"가 없습니다.

alert( "Widget".startsWith("Wid") ); // true, "Widget"은 "Wid"로 시작합니다.
alert( "Widget".endsWith("get") ); // true, "Widget"은 "get"으로 끝납니다.

부분 문자열 추출하기

str.slice(start [, end])

  • 문자열의 start부터 end까지(🚨 end는 미포함)를 반환
let str = "stringify";
alert( str.slice(0, 5) ); // 'strin', 0번째부터 5번째 위치까지(5번째 위치의 글자는 포함하지 않음)
alert( str.slice(0, 1) ); // 's', 0번째부터 1번째 위치까지(1번째 위치의 자는 포함하지 않음)
  • 두 번째 인수가 생략된 경우엔, 명시한 위치부터 문자열 끝까지를 반환
  • startend에 음수를 넘기면 문자열 끝에서부터 카운팅을 시작

str.substring(start [, end])

  • startend 사이에 있는 문자열을 반환
  • substringslice와 아주 유사하지만 startend보다 커도 괜찮음
  • substring은 음수 인수를 허용하지 않고 0으로 처리

str.substr(start [, length])

  • start에서부터 시작해 length 개의 글자를 반환
  • substr은 끝 위치 대신에 길이를 기준 으로 문자열을 추출한다는 점에서 substringslice와 차이
  • 첫 번째 인수가 음수면 뒤에서부터 개수를 카운트함
메서드추출할 부분 문자열음수 허용 여부(인수)
slice(start, end)start부터 end까지(end 미포함)음수 허용
substring(start, end)start와 end 사이(end 미포함)음수는 0으로 취급함
substr(start, length)start부터 length개의 글자음수 허용

문자열 비교하기

  • 문자열을 비교할 땐 알파벳 순서를 기준으로 글자끼리 비교가 이뤄짐
  • 모든 문자열은 UTF-16을 사용해 인코딩 됨
  • str.codePointAt(pos): pos에 위치한 글자의 코드를 반환
  • String.fromCodePoint(code): 숫자 형식의 code에 대응하는 글자를 만들어줍니다.
alert( "z".codePointAt(0) ); // 122
alert( "Z".codePointAt(0) ); // 90
alert( String.fromCodePoint(90) ); // Z

문자열 제대로 비교하기

  • str.localeCompare(str2)를 호출하면 ECMA-402에서 정의한 규칙에 따라 strstr2보다 작은지, 같은지, 큰지를 나타내주는 정수가 반환됨
    - str이 str2보다 작으면 음수를 반환합니다.
    - str이 str2보다 크면 양수를 반환합니다.
    - str과 str2이 같으면 0을 반환합니다.
    alert( 'Österreich'.localeCompare('Zealand') ); // -1

문자열 심화(생략)




📚 참고 : javascript.info

profile
protect me from what i want

0개의 댓글