자바스크립트 톺아보기3

이지영·2024년 12월 26일

3주차 스터디 시작 - 자료 구조와 자료형

👉 5-1. 원시값의 메서드

용어 정리

원시값이란?

숫자, 문자열, 불리언, 심볼 등 변경할 수 없는 값을 의미

예를들어 원시값인 문자열 "Hello"를 수정할 수 없고 새로운 문자열을 생성해야한다.
원시값을 수정하려고 하면 새로운 원시값이 생성된다.

또한 원시값은 스택(stack)메모리에 저장된다.
동일한 원시값을 여러 변수에 할당하면 각 변수는 독립적인 원시값을 가지게된다.

참조값: 참조값은 힙(heap)메모리에 저장되며 변수가 객체를 참조하는 주소를 가진다.
동일한 객체를 여러 변수에 할당하면, 모든 변수가 같은 객체를 참조하게 된다.

원시값참조값
변경 가능 여부변경 불가능변경 가능
저장 위치스택
비교값 비교주소 비교

객체

프로퍼티에 다양한 종류의 값을 저장할 수 있다.
자바스크립트에는 여러 종류의 객체가 있는 함수도 객체의 일종이다.
객체의 장점 중 하나는 함수를 프로퍼티로 저장할 수 있다는 것이다.

ex)

  let john = {
    name: "John",
    sayHi: function(){
      alert("친구야 반갑다")
    }
  };
  john.sayHi()

프로퍼티?

자바스크립트에서 프로퍼티(property)는 객체의 속성이나 특성을 나타낸다.
객체는 키-값 쌍으로 구성되어있으며,
프로퍼티는 키에 해당한다.

ex)
1. 점 표기법

  const person = {
    name: '홍길동',
    age: 30
  }
  console.log(person.name);
  1. 대괄호 표기법
    consloe.log(person['age']);

정리: 프로퍼티는 읽기, 쓰기, 삭제가 가능하며, 객체의 상태나 데이터를 표현하는 데 사용된다.
또한 프로퍼티는 함수나 배열과 같은 다른 데이터 타입을 가리킬 수도 있다.


원시값의 메서드

  • 자바스크립트는 원시값을 마치 객체처럼 다룰 수 있게 해준다.
  • 원시값에도 객체처럼 메서드를 호출할 수 있다.

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

창시자는 다음과 같은 모순적인 상황을 해결해야했다.

  • 문자열이나 숫자와 같은 원시값을 다루어야 하는 작업이 많은데, 메서드를 사용하면 작업을 수월하게 할 수 있을 것 같다.
  • 그런데 원시값은 가능한 한 빠르고 가벼워야한다.
    자바스크립트 창안자는 아래와 같은 방법을 사용해 해결책을 모색하였다.
  1. 원시값은 원시값 그대로 남겨두어 단일 값 형태를 유지

  2. 문자열, 숫자, 불린, 심볼의 메서드와 프로퍼티에 접근할 수 있도록 언어 차원에서 허용

  3. 이를 가능하기 위해, 원시값이 메서드나 프로퍼티에 접근하려면 추가 기능을 제공해주는 특수한 객체,
    "원시 래퍼 객체"를 만들어준다. 이 객체는 곧 삭제된다.

    "래퍼 객체"는 원시 타입에 따라 종류가 다양하다. 각 래퍼 객체는 원시 자료형의 이름을 그대로 차용해,
    String,Number, Boolean,Symbol이라고 부른다.(래퍼마다 제공하는 메서드는 다르다)

    ex)
    str.toUpperCase() - 인수로 받은 문자열의 모든 글자를 대문자로 바꿔주는 메서드

    let str = "Hello";
    alert(str.toUpperCase()); // HELLO
  4. 문자열 str은 원시값이므로 원시값의 프로퍼티(toUpperCase)에 접근하는 순간 특별한 객체가 만들어진다.

❓🤷‍♀️ 여기서 의문?

위에서 프로퍼티란 객체의 속성이나 특성이고, 키에 해당된다고 했는데 원자값은 객체가 아니다.
그렇다면 원시값의 프로퍼티라는 말이 가능한가?

답: 문자열 str의 경우 toUpperCase()메서드와 같은 프로퍼티에 접근하면,
JavaScript엔진이 내부적으로 해당 원시 문자열 값을 감싸는 임시 객체를 생성한다.
이 과정에서 원시값은 객체처럼 동작하지만, 원시값 자체는 여전히 객체가 아니다.
또한 원시값은 키가 없지만, 원시값에 접근할 때 JavaScript는 해당 원시값을 기반으로
생성된 임시 객체에 대해 프로퍼티(예: length, toUpperCase)를 사용할 수 있게 해준다.
따라서 원시값의 프로퍼티는 이러한 메서드나 속성에 대한 접근을 의미한다.

결론

결론적으로 원시값이 직접적으로 프로퍼티를 갖고 있지는 않지만, 원시값에 대한 메서드 호출 시
내부적으로 임시 객체가 생성되어 그 객체의 프로퍼티에 접근하는 방식으로 동작하게 된다.
이는 자바스크립트의 유연한 타입 시스템 덕분이다.

이어서 마저 해보면
1. 문자열 str은 원시값이므로 원시값의 프로퍼티(toUpperCase)에 접근하는 순간 특별한 객체가 만들어진다. 이 객체는 문자열의 값을 알고 있고, toUpperCase()와 같은 유용한 메서드를 가지고 있다.
2. 메서드가 실행되고, 새로운 문자열이 반환 된다.(alert창에 문자열이 출력)
3. 특별한 객체는 파괴되고 str만 남는다.

이러한 내부 프로세스를 통해 원시값을 가볍게 유지하면서 메서드를 호출할 수 있다.
자바스크립트 엔진은 위 프로세스의 최적화에 많은 시경을 쓴다.
원시 래퍼 객체를 만들지 않고도 마치 원시 래퍼 객체를 생성한 것처럼 동작하게끔 해준다.
숫자형도 고유한 메서드를 지원한다.
메서드 toFixed(n)를 이용하면 원하는 자리에서 소숫점 아래 숫자를 반올림 할 수 있다.

  let n = 1.23456;
  alert( n.toFixed(2) ) //1.23

💡 null/undefined는 메서드가 없다.

특수 자료형인 nullundefined의 원시값은 위와 같은 법칙을 따르지 않는다.
이 자료형과 연관되는 래퍼 객체도 없고 메서드도 제공하지 않는다.
어떤 의미에서는 두 자료형이 가장 원시적이라 할 수 있다.
또한 이 두 자료형에 속한 값의 프로퍼티에 접근하려 하면 에러가 발생한다.

Summary

  • null과 undefined를 제외한 원시값에 다양한 메서드를 호출할 수 있다.
  • 원시값에 메서드를 호출하려면 임시 객체가 만들어진다.

👉 5-2. 숫자형

모던 자바스크립트는 숫자를 나타내는 두 가지 자료형을 지원한다.
1. 일반적인 숫자는 "배정밀도 부동수수점 숫자로 알려진 64비트 형식의 IEEE-754에 저장
2. 임의의 길이를 가진 정수는 GigInt숫자로 나타낼 수 있다. (아주 특별한 경우에만 사용하고 큰 숫자를 나타낼때 쓰임)

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

10억을 나타내기
let billion = 1000000000;
이렇게 많은 0을 사용하면 잘못 입력하기 쉽다.
해서 아래와 같은 방법으로 표현한다.

  let billion = 1e9; // 10억, 1과 9개의 9
  alert(7.3e9); // 73억 (7,300,000,000)

음수 표현
let ms = 0.000001

  let ms = 1e-6; 

e측에 음수가 있다면 이 음수의 절다값 만큼 10을 거듭제곱한 수로 나누는 것을 의미한다.
1.23e-6 === 1.23/1000000

16진수, 2진수, 8진수

자바스크립트에서 지원하는 진법은 3개이다. 이 외의 진법을 사용하려면 함수 parseInt를 사용해야한다.

2진수8진수16진수
표현법0b0o0x
사용비트연산, 디지털 회로파일 권한, 특정 비트 연산색상코드, 메모리주소, 비트연산

16진수

alert(0xff) // 255
alert(oxFF) // 255 대소문자를 가리지 않으므로 둘 다 같은 값

2진수와 8진수는 드물게 쓰이간 하지만 0b 와 0o를 사용해 간단히 나타낼 수 있다.

  let a = 0b11111111; //255 의 2진수
  let b = 0a377; /// 255의 8진수
  alert(a==b); //true, 진법은 다르지만 a와 b는 같은 수임

toString(base)
num.toString(base)메서드는 base 진법으로 num을 표현한 후 이를 문자형으로 변환해 반환한다.

  let num = 255;
  alert(num.toString(16)); // ff
  alert(num.toString(2); // 11111111

base는 2에서 36까지 쓸 수 있는데 기본값은 10이다.
base별 유스 케이스:
base 16 = 16진수
base 2 = 비트 연산 디버깅에 주로 쓰인다 숫자는 0 또는 1
base 36 = 사용할 수 있는 base의 최댓값으로 0..9와 A..Z를 사용해 숫자를 표현
알파벳 전체가 숫자를 나타내는 데 사용된다. url을 줄이는 것과 같이 숫자로 된 긴 식별자를 짧게 줄일 떄 유용하다.

ex)

  alert(123456..toString(36)) // 2n9c

위의 예시처럼 숫자를 대상으로 메서드 toString을 호출하고 싶다면 숫자 다음에 점 두개..를 붙여야한다.
점을 하나만 사용한다면 소수부로 인식되어 에러가 발생할 수 있다.

어림수 구하기

어림수를 구하는 것(rounding)은 숫자를 다룰 때 가장 많이 사용되는 연산 중 하나이다.
어림수 관련 내장함수
1. Math.floor
소숫점 첫째 자리에서 내림(버림)

  • 양수일 경우: 소수점 이하를 내림하여 정수를 반환
    ex) Math.floor(1.9)는 1을 반환
  • 음수일 경우: 소수점 이하를 내림하여 정수를 반환하는데 여기서 내림은 더 작은 값으로 가는 것을 의미한다.
  • 즉 음수의 경의 절댓값이 큰 쪽으로 내려가게된다.
    • ex) -1.1 은 -2를 반환
    • -1.1보다 작거나 가장 큰 정수를 찾는데 이는 -2이다.
  1. Math.ceil
    소숫점 첫째 자리에서 올림
    음수일 경우: 그 숫자를 올림하여 더 작은 변수로 변환한다. 즉 절대값이 더 작은 값이 반환된다.
  2. Math.round
    소숫점 첫째 자리에서 반올림
  3. Math.truc(Explorer에서는 지원하지 않음)
    소수를 무시
Math.floorMath.ceilMath.roundMath.truc
3.13433
3.63443
-1.1-2-1-1-1
-1.6-2-2-1

비슷한 것으로 toFixed가 있는데 주의할 점은 이 메서드의 반환 값이 문자열이라는 것이다.

부정확한 계산

숫자는 내부적으로 64비트 형식으로 표현되기 떄문에 숫자를 저장하려면 정확히 64비트가 필요하다.
64비트 중 52비트는 숫자를 저장하는 데 사용되고, 11비트는 소수점 위치를(정수는 0) 1비트는 부호를 저장하는 데 사용된다.
그런데 숫자가 너무 커지면 64비트으 공간이 넘쳐서 infinity로 처리된다.

isNaN과 isFinite

  • Infinity와 -Infinity 그 어떤 숫자보다 큰 혹은 작은 특수 숫자 값

  • NaN 에러를 나타내는 값

      alert(isNaN(NaN)); // true
      alert(isNaN("str")); // false
      alert(NaN === NaN); // false

    인수를 숫자로 변환한 다음 NaN인지 테스트함.
    하지만 NaN은 자기 자신을 포함하여 그 어떤 값과도 같지 않다.

    isFinite: 인수를 숫자로 변환하고 변환한 숫자가
    NaN/Infinity/-Infinity가 아닌 일반 숫자인 경우 true를 반환함.

      alert(isFinite("15")); // true
      alert(isFinite("str")) // false
      alert(isFinite(Infinity)) // false

    isFinite는 문자열이 일반 숫자인지 검증하는 데 사용하곤 한다.

    let num = +prompt("숫자를 입력하세요",'');
    // 숫자가 아닌 값을 입력하거나 Infinity, -Infinity를 입력하면 false가 출력
    alert(isFinite(num);

    빈 문자열이나 공백만 있는 문자열은 isFinite를 포함한 모든 숫자 관련 내장 함수에서 0으로 취급된다

    Object.is 와 === 비교하기

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

    1. NaN을 대상으로 비교할 때
    2. 0과 -0이 다르게 취급되어야할 때
      두가지 경우에는 Object.is를 사용하면 1. true, 2.false가 나온다.
      이 두가지 에지 케이스르 ㄹ제외하고는 Object.is와 === 의 ㄱㄹ과는 같다.

parseInt와 parseFloat

단항 덧셈 연산자 = 또는 Number() 를 사용하여 숫자형으로 변형할 때
적용되는 규칙은 꽤 엄격하다.
피연산자가 숫자가 아니면 형 변환이 실패한다.

  alert(+"100px"); // NaN

엄격한 규칙이 적용되지 않는 유일하 ㄴ예외는 문자열의 처음 또는 끝에 공백이 있어서 공백을 무시할 때이다.
그런데 실무에서는 css등에서 100px, 12pt와 같이 숫자와 단위를 함께 쓰는 경우가 흔하다.
숫자만 추출하는 방법이 필요해서 내장함수 parseInt, parseFloat가 만들어졌다.

두 함수는 불가능할 때까지 문자열에서 숫자를 읽는다. 숫자를 읽는 도중 오류가 발생하면 이미 수집된 숫자를 반환한다.
parseInt는 정수, parsePloat는 부동 소수점 숫자를 반환한다.

alert( parseInt('100px') ); // 100
alert( paseFloat('12.5em') ); // 12.5
alert( paseInt('12.3;) ); // 12 정수 부분만 반환
alert( parseFloat(''12.3.4) ); // 12.3 두번째 점에서 숫자 읽기를 멈춤

pareInt와 parseFloat가 NaN을 반환할 때도 있다.

  alert( parseInt('a123') ); // NaN , a는 숫자가 아니므로 중지된다.

parseInt(str,radix)의 두 번째 인수
parseInt()의 두 번째 매개 변수는 선택적으로 사용할 수 있다.
radix는 원하는 진수를 지정해줄 때 사용한다.
따라서 parseInt를 사용하면 16진수 문자열, 2진수 문자열 등을 파싱할 수 있다.

alert( parseInt('0xff',16) ); // 255
alert( parseInt('ff'), 15 ); // 255
alert( parseInt('2n9c') ); // 123456

기타 수학 함수

자바스크립트에서 제공하는 내장 객체 Math엔 다양한 수학 관련 함수와 상수들이 들어있다.

Math.random()
0과 1사이의 난수를 반환한다.(1은 제외) // 0.23415234 등

Math.max()
Math.min()
인수 중 최대/최솟값을 반환

Math.pow(n,power)
n을 power번 거듭제곱한 값을 반환한다.

  alert( Math.pow(2,10) ); //2의 10제곱 1024

Summary

  • 0이 많이 붙은 큰 숫자는 e를 사용한다.
  • 0의 개수를 e 뒤에 추가 123e6 = 123000000
  • e다음에 음수가 오면 음수의 절댓값 만큼 10만큼 거듭제곱한 숫자로 주어진 숫자를 나눈다.
    123e-6 0.000123

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

  • 자바스크립트는 특별한 변환 없이 16진수, 8진수, 2진수를 바로 사용할 수 있게 지원한다.
  • 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()를 사용하면 어림수를 구할 수 있다.
  • 소수를 다룰 땐 정밀도 손실에 주의해야한다.

과제

  1. 사용자에게 두 수를 입력받고, 두 수의 합을 출력해주는 스크립트를 작성해보세요
let firstInputNumber = prompt("첫 번째 수를 입력해주세요",'');
let secoundInputNumber = prompt("두 번째 수를 입력해주세요",'');
alert(parseInt(firstInputNumber)+parseInt(secountInputNumber);

정답
let a = +prompt("첫 번째 수를 입력해주세요", "");
let b = +prompt("두 번째 수를 입력해주세요", "");
alert(a + b);

두 방식의 차이
pareInt()함수를 사용하여 입력된 문자열을 정수로 변환하였다.
이 경우 소수점 이하가 있는 숫자는 버려진다.
하지만 +연산자를 이용하면 소수점 이하도 포함된 숫자를 처리할 수 있다.
따라서 +연산자를 사용하는 방법이 더 적합하다.

  1. 6.35.toFixed(1) == 6.3인 이유는 무엇일까요?
    toFixed는 첫번째 소수점을 제외하고 버림처리를 한다. 따라서 6.3이다.

👉 5-3. 문자열

자바스크립트엔 글자 하나만 저장할 수 있는 별도의 자료형이 없다.
텍스트 형식의 데이터는 길이에 상관없이 문자열 형태로 저장된다.
자바스크립트에서 문자열은 페이지 인코딩 방식과 상관없이 항상 UTF-16형식을 따른다.

백틱

표현식을 ${}으로 감싸고 이를 백틱으로 감싼 문자열 중간에 넣어주면 해당 표현식을 문자열 중간에 쉽게 삽입할 수 있다.
이러한 방식을 **템플릿 리터럴(template literal)이라고 부른다.
백틱을 사용하면 여러 줄의 문자열을 만들 수 있다.

특수 문자

\n 줄바꿈

\r 캐리지 리턴 window에서는 캐리지 리턴과 줄 바꿈 특수 문자를 조합해 줄을 바꾼다.(\r\n)단독으로 사용하는 경우는 없다.

\', \" 따옴표

\ 역슬래시

\t 탭

\b, \f, \v 각각 백스페이스 폼피드 세로탭을 나타낸다. 요즘에는 사용하지 않는다.

\xXX 16진수 유니코드 XX로 표현한 유니코드 글자(예시: 알파벳 'z'는 '\x7A'와 동일)

\uXXXX UTF-16 인코딩 규칙을 사용하는 16진수 코드 XXXX로 표현한 유니코드 기호

XXXX는 반드시 네 개의 16진수로 구성되어야한다. (예시: \u00A9는 저작권 기호의 유니코드임)

모든 특수문자는 '이스케이프 문자(escape character)'라고 불리는 역슬래시로 시작한다.

역슬래시는 문자열 내 따옴표를 넣을 때 사용할 수 이다.

특정 글자에 접근하기

문자열 내 특정 위치인 pos에 있는 글자에 접근하려면 [pos]같이 대괄호를 이용하거나
str.charAt(pos)라는 메서드를 호출해야한다. 위치는 0부터 시작한다.

let str = `Hello`;
// 첫 번째 글자
alert(str[0]) // H
alert(str.charAt(0)) //H
// 마지막 글자
alert(str[str.lenght -1]); //o

근래에는 대괄호를 이용하는 방식을 사용한다.
두 접근방식의 차이는 반호나할 글자가 없을 때 드러난다.
접그하려는 위치에 글자가 없는 경우 []는 undefined를 charAt은 빈 문자열을 반환한다.

for..of를 사용하면 문자열은 구성하는 글자를 대상으로 반복 작업을 할 수 있다. 
for(let char of "Hello"){
  alert(char); // H,e,l,l,0 (char은 순차적으로 H,e,l,l,o가 된다.)
}

문자열의 불변성

문자열은 수정할 수 없다. 따라서 문자열의 중간 글자 하나를 바꾸려고 하면 에러가 발생한다.
따라서 완전히 새로운 문자열을 하나 만든 다음, 이 문자열을 str에 할당하면 된다.

let str = 'Hi';
str = 'h' + str[1]; // 문자열 전체를 교체함
alert(str); // hi

대 소문자 변경하기
메서드 toLowerCase()와 toUpperCase()는 대문자를 소문자로, 소문자를 대문자로 변경(케이스 변경)시켜준다.
글자 하나의 케이스만 변경하는 것도 가능하다.
alert('Interfase'[0).toLowerCase()); // 'i'

부분 문자열 찾기

문자열에서 부분 문자열(substring)을 찾는 방법은 여러 가지가 있다.
str.indexOf 메서드 이용하기
이 메서드는 문자열 str의 pos에서부터 시작해, 부부 문자열 substr이 어디에 위치하는지를 찾아준다.
원하는 부분 문자열을 찾으면 위치를 반환하고 그렇지 않으면 -1 을 반환한다.

let str = 'Widget with id';
alert(str.indexOf('Widget)); // 0
alert(str.indexOf('widget')); // -1 indexOf는 대소문자를 따지므로 원하는 문자열을 찾지 못함
alert(str.indexOf('id')) // 1 'id'는 첫 번째 위치에서 발견됨

str.indexOf(subtr,pos)의 두 번째 매개변수 pos는 선택적으로 사용할 수 있다.
이를 명시하면 검색이 해당 위치부터 시작된다.

let str = 'Widget with id';
slaert(str.indexOf('id',2); // 12

다음과 같이 문자열 내 부분 문자열 전체를 대상으로 무언가를 하고싶다면 반복문 안에 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.indexOf9target,pos) ;
    if(foundPos == -1) break;

    alert(`위치: ${foundPos}`);
    pos = foundPos + 1; // 다음 위치를 기준으로 검색을 이어간다.
  }

동일한 알고리즘을 사용해 코드만 짧게 줄이려면 아래와 같다.

  let str = "As sly as a fox, as string as an ox";
  let arget = "as";
  let pos = -1;
  while((pos = str.indexOf(target,pos+1) != -1){
    alert(`위치 ${pos}`);
  }

0개의 댓글