자바스크립트 완벽 가이드(JavaScript: The Definitive Guide 7/E) - 3장 : 타입, 값, 변수

SangHyun Park·2023년 10월 31일
0

JavaScript

목록 보기
6/9
  • 프로그래밍 언어에서 표현하고 조작할 수 있는 값의 종류를 타입이라 부른다.
  • 변수에는 이름이 있으며 프로그램은 변수 이름을 통해 값을 참조

3. 1 개요와 정의

  • 타입은 기본 타입객체 타입 둘로 나뉨
  • 기본 타입 : 숫자, 문자열, 불, null, undefined, Symbol
  • null과 undefined 기본 값이지만 숫자나 문자열, 불에 속하지 않음
  • 객체 타입 : 기본 타입에 속하지 않는 모든 값
    • 객체는 객체 타입의 멤버이며 프로퍼티의 집합이다. 각 프로퍼티에는 이름과 값이 있고, 기본 값 또는 다른 객체이다.
    • 일반적으로 JS 객체는 이름 붙은 값의 순서 없는 집합.
  • 자바스크립트 인터프리터는 자동으로 가비지 컬렉션을 수행해 메모리를 관리하기에 따로 파괴하거나 할당을 해제할 필요가 없다. 가끔은 할 때도 있음
  • JS는 객체 지향 프로그래밍 스타일을 지원한다. 함수가 다양한 타입의 값을 다루는 것이 아니라 객체(타입 자체)에 그 값을 다루는 메서드를 정의한다.
    • 예를 들어 a의 요소를 정렬할 때 a를 sort() 함수에 전달하지 않는다. 대신 a의 sort() 메서드를 호출한다.

      a.sort() // sort(a)의 객체 지향 버전
    • 엄밀히 말해 JS에서는 오직 객체만 메서드를 가질 수 있다. 하지만 숫자, 문자열, 불, 심벌로 마치 메서드가 있는 것처럼 동작

    • null, undefined 는 제외

  • 객체 타입은 가변(mutable)이며 기본 타입은 불변(immutable)
  • JS는 값의 타입을 자유롭게 변환한다. 문자열이 와야할 곳에 숫자를 쓰면 자동으로 문자열로 변환 하는 것 처럼.
  • 이 때문에 동등연산자 사용 권장(===)

3. 2 숫자

  • Number는 정수와 함께 실수를 대략적으로 표현
  • IEEE 754 표준에서 정의하는 64비트 부동 소수점 형식을 사용.
  • 최대+-1.7976931348623157 100^308, 최소 +-5 10^(-324) 범위의 숫자를 표현 가능
  • JS 프로그램에 직접 기입한 숫자를 숫자 리터럴이라 부른다.

3. 2. 1 정수 리터럴

  • 10진수, 16진수 값도 인식.

3. 2. 2 부동 소수점 리터럴

  • 부동 소수점 리터럴은 지수 표기법으로도 표현 가능.
  • [digits][.digits][(E|e)[(+/-)digits]
3.14
123.1231
.3123123123
6.02e23 // 6.02 * 10^23
1.123E-32 // 1.123 * 10^(-32)

let billion = 1_000_000_000; 
let bytes = 0x89_AB_CD_EF;
let bits = 0b0001_1101_0111; // 4비트 구분자
let fraction = 3.123_345_123;

3. 2. 3 JS의 산술 연산

  • 산술 연산자를 통해 숫자를 조작
  • +, -, *, /, %, **(ES2016)
  • 이 것 이외에도 Math객체의 프로퍼티로 정의된 함수와 상수를 통해 복잡한 계산 지원
  • 산술 과정에서 0으로 나누거나 오버플로, 언더플로가 발생해도 에러를 이르키지 않는다.
  • 표현범위보다 큰경우(언더플로) Infinity를 반환
  • 작은 경우 -Infinity
  • 언더플로는 계산 결과가 JS가 표현할 수 있는 가장 작은 숫자보다도 0에 가까울 때 일어난다. 이런경우 0을 반환. 음수에서 언더플로가 일어나면 JS는 ‘음의 0’이라는 특별한 값을 반환한다. 일반적으로 0과 거의 같으며 신경 안써도됨.
  • JS에서는 0으로 나눠도 에러가 나지 않고 무한대 또는 음의 무한대로 반환. 예외는 0을 0으로 나눌때 이결과 NaN(숫자가 아님)
  • 무한대를 무한대로 나누거나, 음수의 제곱근, 숫자가 아니고 숫자로 변환할 수 없는 피연산자에 산술 연산자를 적용하려 할 때도 NaN
  • NaN 값은 자기 자신을 포함한 어떤 값과 같지 않다는 특성으로 Number.isNaN(x)를 써야 한다.

3. 2. 4 이진 부동 소수점 숫자와 반올림 오류

  • 실수는 무한히 많지만, JS의 부동 소수점 형식으로 정확히 표현할 수 있는 숫자는 유한하다. 그래서 JS로 실수를 다룰 때 실제 숫자의 근삿값으로 표현될 때가 많다.
  • IEEE754 부동 소수점 표현은 이진 표현이며 1/2, 1/8, 1/1024를 정확히 표현할 수 있지만. 1/10, 1/100은 그렇지 않다. 0.1 같은 단순한 숫자를 정확히 표현 불가하다. 아주 가깝게 표현
  • 정수로 변환하는 것을 고려. 0.1달러 대신 10센트로 계산하는 것처럼

3. 2. 5 BigInt로 임의 정확도를 부여한 정수

  • 값이 정수인 숫자 타입.
  • 다른 프로그래밍 언어나 API와 호환에 필요한 64비트 정수를 표현하기 우해 추가
  • BigInt는 타이밍 공격을 방지할 수 없으므로 암호화에는 사용할 수 없다.
  • 연속된 숫자 다음 n을 붙인 형식
1234n
0b11111n // 이진 BigInt
0o7777n // 8진 BigInt

BigInt(123123)
  • 일반적인 산술 연산과 비슷하지만 나눗셈시 나머지를 버린다.
  • BigInt 피연산자와 일반적인 숫자 피연산자를 섞어 쓸 수 없다.
  • 비교 연산자는 피연산자를 섞어도 괜찮다.

3. 2. 6 날짜와 시간

  • Date클래스는 날짜와 시간에 대응하는 수자를 표현하고 조작
let timestamp = Data.now();
let now = new Data();

3. 3 텍스트

  • 텍스트를 표현하는 타입은 문자열
  • 문자열은 16비트 값이 순서에 따라 이어진 형태, 기본 값이므로 불변이다.
  • 각 값은 일반적으로 유니코드
  • 인덱스틑 0에서 시작

3.3.1 문자열 리터럴

  • 문자열을 사용할 때 ‘’, “”, `` 쌍으로 묶으면 된다.
  • ES5 부터 각 행의 마지막에 역슬래시()를 쓰면 문자열 리터럴을 여러 행으로 구분 할 수 있다.
"one\
long\
line"

3.3.2 문자열 리터럴 안의 이스케이프 시퀀스

  • 문자열에서 역슬래쉬()는 특별한 의미를 같는다.
  • \ 과 다음 문자의 조합으로 문자열에 일반적으로 표현할 수 없는 문자를 표현한다.

3.3.3 문자열 다루기

  • 문자열을 병합하는 기능이 내장되어 있다. + 연산자를 사용
  • 문자열의 길이는 length 프로퍼티로 알 수 있다.
  • 문자열은 불변이므로 replace()나 toUpperCase()는 문자열을 수정하는 것이 아닌 새 문자열을 반환 하는것.

3.3.4 템플릿 문자열

  • ES6부터 백틱으로 갑싼 문자열 리터럴 사용 가능
let name = "bill";
let greeting = `Hello ${name}.`;
  • ${ } 안에 있는 것은 모두 JS 표현식으로 해셕된다.

3.3.5 패턴 매칭

  • 문자열 내부의 패턴을 정의하고 매칭하는 정규 표현식이라는 데이터 타입이 있다.
  • 슬래시 한 쌍 사이에 텍스트를 쓰는 형태
/^HTML/; // 문자열의 시작 부분에 있는 H T M L 에 일치합니다.
/[1-9][0-9]*/; // 0이 아닌 숫자하 하나 있어야 하고 그 뒤에는 숫자 제한이 없다.
/\bjavascript\b/i; // javascript가 한단어 들어가야하고 대소문자를 구분하지 않는다.

3.4 불 값

  • 참 또는 거짓을 표현
  • JS 값은 모두 불 값으로 변환 될 수 있다.
  • 불 값은 toString() 메서드가 있지만 그 외에 유용한 메서드는 없다.
  • && : AND 연산
  • || : OR 연산
  • ! : NOT 연산

3.5 null과 undefined

  • null은 값이 없음을 나타낼 때. ‘객체가 없다’는 것을 나타내는 특별한 객체
  • undefined는 초기화 됮 않는 변수의 값이며 존재하지 않는 객체 프로퍼티나 배열 요소에 접근 했을 때 반환되는 값.
  • 불 값에서는 둘다 false로 동작

3.6 심벌

  • 심벌(Symbol)은 문자열이 아닌 프로퍼티 이름.
  • 이를 이해하기 위해서는 JS의 객체 타입이 프로퍼티의 순서 없는 집합이며 각 프로퍼티에 이름과 값이 있다는 것을 이해해야 함.
  • 프로퍼티 이름은 일반적으로 문자열
  • ES6이후에는 심벌 역시 문자열과 같은 목적으로 사용 가능
let strname = "string name";
let symname = Symbol("propname");
let o = {};
o[strname] = 1
o[symname] = 2;
  • 심벌은 리터럴 문자열이 없고 Symbol() 함수로 호출 한다. 이 함수는 절대 같은 값을 반환 하지 않는다.
  • 그렇기에 기존 프로퍼티와 같은 이름으로 덮어 쓸 염려 없이 프로퍼티 추가 가능하다.
let s = Symbol("sym_x");
s.toString(); // => "Symbol(sym_x)"

심벌 관련 함수 두개

  • Symbol.for() : 문자열 인자를 받고 그 문자열과 연관된 심벌 값을 반환. 존재하지 않으면 새 심벌을 생성해 반환하고, 있으면 기존 심벌을 반환. Symbol()과 완전 다름. Symbol()은 절대 같은 값을 반환하지 않지만, Symbol.for()는 같은 문자열을 호출했을 때 항상 같은 값을 반환.
  • Symbol.ketFor() : 반환된 심벌의 문자열을 얻을 수 있음
let s = Symbol.for("shared");
let t = Symbol.for("shared");
s === t; // => true
s.toString(); // "Symbol(shared)"
Symbol.keyFor(t); // "shared"

3.7 전역 객체

  • 전역 객체는 일반적인 JS 객체지만 중요한 목적에 쓰임.
  • 이 객체의 프로퍼티는 전역으로 정의된 식별자.
  • 전역 객체의 초기 프로퍼티는 예약어가 아니지만 예약어로 간주
  • 노드의 전역 객체에는 이름이 global인 프로퍼티, 웹 브라우져는 Window 객체

3.8 불변인 기본 값과 가변인 객체 참조

  • 문자열은 문자의 배열이라고 생각이 되지만 JS는 문자열의 변화를 허용하지 않는다. 몇몇 메서드들은 수정된 것처럼 보이지만 새 문자열 값을 반환 하는 것이다.
  • 객체는 가변이므로 값을 바꿀 수 있다.
  • 객체는 값으로 비교하지 않고 두 객체의 프로퍼티와 값이 같다고 해서 같은 객체가 아니다! 또한 두 배열에 같은 요소가 같은 순서로 존재한다고 해서 둘이 같은 배열이 아니다.
let o2 = { x: 1 }, p = { x: 1 };
o === p // => false
let a = [], b = [];
a === b // => false
  • 객체는 기본 타입과 구별하기 위해 참조 타입이라 부를 때도 있다.
  • 객체는 참조로 비교한다는 뜻이며 두 객체가 값이 같다는 말은 오직 두값이 같은 객체를 참조 할때만 성립!
let a = [];
let b = a;
b[0] = 1;
a[0]; // => 1
a === b; // => true
  • 객체나 배열의 사본을 만들기 위해서는 반드시 객체 프로퍼티나 배열 요소를 직접 복사
let a = ["a", "b", "c"];
let b = [];
for (let i = 0; i < a.length; i++) {
  b[i] = a[i];
}

let c = Array.from(b);

3.9 타입 변환

  • JS는 값의 타입을 강제하지 않는다.
  • 불 값을 예상하는 곳이라 해도 아무 타입이나 써도 된다.

(표 3-2)

3.9.1 변환과 일치

  • 일치연산자 (===)를 써야한다.
  • 동등연산자(==) 는 타입 변환 후 연산을 진행하기에 예상치 못한 에러가 나올 수 있다.

3.9.2 명시적 변환

  • JS는 타입을 자동적으로 변환해주지만 때로는 직접 하기도 한다.
  • Boolean(), Number(), String()이 있다.
  • Number 클래스에는 소수점 아래 숫자를 몇자리를 표기할 지 정하는 toFixed()가 있다. 다양한 메서드 활용

3.9.3 객체를 기본 값으로 변환

  • JS가 객체를 기본 값으로 변환할 때 사용하는 복잡한 규칙
  • 복잡한 이유는 일부 객체는 여러 가지 기본값으로 표현될 수 있기 때문이다.

문자열 선호

  • 기본 값으로 반환할 때 문자열로 변환할 수 있다면 문자열 값을 우선 시 하는 알고리즘

숫자 선호

  • 기본 값으로 반환할 때 숫자로 변환할 수 있다면 숫자 값을 우선 시 하는 알고리즘

선호 없음

  • 어떤 기본 타입을 선호하는지 정해 놓지 않음, 클래스에서 변환 방법을 정의할 수 있다. 내장 타입 중 Date를 제외한 모든 타입이 이 알고리즘에 숫자 선호를 적용, Date는 이 알고리즘에 문자열 선호를 적용

객체를 불로 변환

  • 객체는 모두 true로 변환. 이 변환은 알고리즘이 적용되지 않으며 모든 객체에 적용. 빈 배열, 래퍼 객체 new Boolean(false) 까지 true

객체를 문자열로 변환

  • 객체 → 문자열, 먼저 문자열 선호 알고리즘을 사용해 기본 값으로 변환한 다음, 필요하면 표 3-2에 규칙에 따라 기본 값을 문자열로 변환.
  • 문자열 인자를 예상하는 내장 함수에 객체를 전달할 때, String()을 변환 함수로 호출할 때, 템플릿 리터럴에 객체를 사용할 때 이런 변환에 해당.

객체를 숫자로 변환

  • 숫자 선호 알고리즘에 따라 기본값→표3-2규칙에 따라 숫자로 변환
  • 숫자 인자를 예상하는 JS 내장 하뭇와 메서드가 객체로 받았을 때 사용. 예외 있음

특별한 케이스인 연산자 변환

  • 이 케이스는 위의 사항을 따르지 않는 예외 사항
    • 연산자는 숫자를 더하고, 문자열을 병합한다. + 연산자의 피연산자 중 하나가 객체라면 JS는 해당 피연산자를 선호 없을 알고리즘에 따라 기본 값으로 변환 → 알고리즘을 적용해 두 개의 기본 값이 남으면 타입을 체크. 피연산자 중 하나라도 문자열이면 다른 하나도 문자열로 변환한다음 병합. 모두 문자열이 아니면 숫자로 변환한 다음 더하기
  • ==, ≠ 연산자는 타입 변환 허용
  • 관계 연산자는 피연산자의 순서를 비교하며 숫자와 문자열을 비교 할 수 있음.
  • 자세한 사항은 3.9 타입 변환 참고

toString()과 valueOf() 메서드

  • 모든 객체는 기본 값으로 변환 할 때 사용하는 두가지 변환 메서드를 상속.
  • toString은 객체의 문자열 표현을 반환.
({ x: 1, y: 2 }).toString(); // => "[object Object]"
  • valueOf()는 객체를 표현하는 기본 값이 존재한다면 그 값으로 객체를 변환.
  • 하지만 객체는 복잡한 형태이므로 단일한 기본 값으로 표현하는 것은 불가능.
  • 그렇기에 기본 valueOf() 메서드는 기본 값이 아니라 객체 자체를 반환.
  • 문자열, 숫자, 불의 래퍼 클래스의 valueOf()는 기본 값을 반환.
  • 배열, 함수, 정규 표현식은 기본 메서드를 상속. 이들 타입의 인스턴스에서 valueOf()를 호출 하면 객체 자체를 반환

객체에서 기본 값으로 변환하는 알고리즘

  • 문자열 선호 알고리즘은 toString()을 시도. 정의 되어 있고 기본 값을 반환한다면 JS는 그값을 사용. 설령 문자열이 아니더라도.
  • toString()이 존재하지 않거나 객체를 반환한다면 valueOf()를 시도.
  • 둘다 안되면 TypeError
  • 문자열 선호는 valueOf()먼저 toString() 시도
  • 선호 없음 알고리즘은 변환하는 객체의 클래스에 따라 다르게 동작, Date 객체일 경우 문자열 선호, 그 외에 객체에는 숫자 선호.
Number([]); // => 0
Number([99]); // => 99

3.10 변수 선언과 할당.

  • 값에 이름을 부여하면 그 값을 참조하고 사용 가능 이 과정을 변수에 값을 할당한다고 말한다.
  • 값에 이름을 영구히 할당할 때는 변수 대신 상수

3.10.1 let과 const를 사용한 선언

  • 가능하면 번수를 선언할 때 초싯값을 할당. 그렇지 않다면 할달 할 때 까지 undefined로 남음
  • const는 상수이기에 바꾸려고 하면 TypeError

변수와 상수 스코프

  • 변수의 스코프는 프로그램 소스 코드에서 해당 번수가 정의된 영영
  • let과 const는 블록 스코프를 가짐. 이 말은 let이나 const 문이 존재하는 블록 안에서만 해당 변수와 상수가 유효하다는 뜻
  • if/esle 문, while, for 바디 역시 블록. { 블록 }
const x = 1;
if (x === 1) {
  let x = 2;
  console.log(x); // 2
}

console.log(x); // 1
let x = 3; // 에러

선언과 타입

  • js 변수 선언에는 타입 지정하지 않는다.

3.10.2 var를 사용한 변수 선언

  • var로 선언한 변수는 함수 바디 스코프를 가진다.
  • 함수 바디 바깥에서 var를 사용하면 전역 변수로 선언. var로 선언된 전역 변수는 전역 객체의 프로퍼티로 존재. globalThis로 참조 가능.
  • var는 let과 달리 같은 변수를 몇 번이고 선언 가능.
  • 호이스팅 기능의 존재. 이 선언문은 함수의 맨 위로 끌어올려짐. 코드상 위치는 그대로 이지만 정의만 함수 맨 위로 올라감. 어디서든 에러없이 사용 가능 한데 이는 버그의 원인이 됨

3.10.3 분해 할당

  • 오른쪽 값에서 하나 이상의 값을 추출해 왼쪽에 있는 변수에 할당.
let [x, y] = [1, 2];

let [r, theta] = toPolar(1.0, 1.0);

let o = { x: 1, y: 2 };
for (const [name, value] of Object.entries(o)) {
  console.log(name, value);
}

let [x, ...y] = [1, 2, 3, 4]; // y == [2,3,4]
profile
마라토너

0개의 댓글