해당 포스트는 자바스크립트 완벽 가이드라는 교재로 스터디를 하면서 3장을 정리한 포스트입니다.
주관적으로 해석한 내용이 들어가 있어서 잘못된 내용이 포함될 수 있습니다.
📌 3장의 정리 및 핵심
- 자바스크립트에서 숫자와 문자열을 만들고 조작하는 방법
- 자바스크립트의 다른 기본 타입인
boolean
,Symbol
,null
,undefined
를 다루는 법 - 불변인 기본 타입과 가변인 참조 타입의 차이
- 자바스크립트가 묵시적으로 타입을 변환하는 과정, 그리고 프로그램에서 직접 변환하는 방법
- 상수와 변수를 선언하고 초기화하는 방법, 선언하는 변수와 상수의 어휘적 스코프
기본 타입은 어떤 방법으로도 변경이 불가능합니다.
변경된 것처럼 보인다면 그것은 변경이 아닌 새로운 값을 만든것입니다.
📌 타입
프로그래밍 언어에서 표현하고 조작할 수 있는 값의 종류
1. 기본 타입
기본 타입은 값이 불변입니다.
number
string
boolean
null
undefined
Symbol
2. 객체 타입
기본 타입을 제외한 모든 타입들은 객체 타입입니다.
객체 타입은 값이 가변입니다.
array
, function
, class
, Set
, Map
, Date
등이 있습니다.
📌 리터럴
직접 기입한 값
1
2
3
4
5
6
// 아래에서 10과 20이 숫자 리터럴
// 유사한 형태로 문자열, 객체, 함수 등의 리터럴이 존재함
let number = 10;
number = 20;
const num = number;
📌 number 타입
1. 특별한 number 타입들
Infinite
, -Infinite
, NaN
Number.isNaN()
vsisNaN()
1
2
3
4
5
isNaN("1"); // false
isNaN("1a"); // true
Number.isNaN("1"); // false
Number.isNaN("1a"); // false
2. 특이한 number 타입 비교
소수점을 정확하게 표현하지 못해서 비교는 하지 않는 것이 좋다.
1
2
3
4
const n1 = 0.3 - 0.2; // 0.09999999999999998
const n2 = 0.2 - 0.1; // 0.1
console.log(n1 === n2); // false
3. BigInt
천문학적으로 큰 수라면 BigInt
를 사용하면 됩니다.
📌 string 타입
핵심은 문자열은 불변이라 변할 수 없습니다.
문자열이 배열과 같이 생겨서 배열처럼 변경할 수 있다고 느껴질 수 있습니다.
하지만 문자 타입은 기본 타입이라 불변한 값입니다.
1
2
3
let str = "apple";
str[0] = "b";
console.log(str); // "apple"
혹시 “문자 메서드로 문자를 변경하는 것이 아닌가?”라고 생각할 수 있지만 그것 또한 원본은 놔두고 복사본을 생성하는 것입니다.
결국 중요한 것은 “메서드로 처리한 값들은 결국 새로운 문자열을 만들어서 반환하는 것이다.”라는 점입니다.
1
2
3
let str = "apple";
str.replaceAll("p", "q");
console.log(str); // "apple"
📌 null vs undefined
null
은 의도적으로 값이 없음을 의미
undefined
는 초기화되지 않은 변수의 값 혹은 존재하지 않는 프로퍼티나 배열 요소에 접근한 경우 반환하는 값
typeof null === "object"
는 초기 자바스크립트의 버그라고 합니다.
📌 Symbol
리터럴이 존재하지 않습니다.
객체의 키로 사용할 수 있는 값입니다.
Symbol
은 객체의 키중에서 충돌하지 않는 유일한 값으로 사용하기 위해서 개발되었습니다.
이게 무슨말이냐면 ES6
의 이터러블을 도입할 때 객체마다 겹치지 않는 유일한 이름의 key
가 필요했습니다.
만약에 iterator
이라는 키를 사용해버리면 기존에 존재했고 iterator
이라는 키를 사용하는 객체는 키가 중복되어버려서 제대로 된 동작을 할 수 없습니다.
그렇기 때문에 Symbol
이라는 타입을 만들고 객체의 key
로 사용할 수 있으며, 기존의 어떤 값과도 중복될 수 없는 유니크한 값을 만들도록 개발했습니다.
📌 falsy한 값
boolean
타입으로 변환하면 false
가 되는 값들입니다.
undefined
null
0
,-0
NaN
""
빈 객체({}
), 빈 배열([]
)은 포함되지 않습니다.
📌 전역 객체
- 기본적으로 갖는 값들
- 전역 상수 (
null
,NaN
,Infinite
) - 전역 함수 (
isNaN()
,parseInt()
) - 생성자 함수 (
Date()
,Number()
,Object()
) - 전역 객체 (
Math
,JSON
)
- 전역 상수 (
브라우저의 전역 객체는 window
, 노드의 전역 객체는 global
, 표준 globalThis
입니다.
📌 객체 타입은 참조 값을 가진다.
배열과 객체는 참조 값을 갖기 때문에 같으려면 동일한 참조 값을 가져야 한다.
1
2
3
4
5
6
7
8
9
10
11
const arr1 = [];
const arr2 = [];
console.log(arr1 === arr2); // false
const arr3 = arr1;
arr3[0] = 10;
console.log(arr1[0]); // 10
console.log(arr3[0]); // 10
console.log(arr1 === arr3); // true
📌 명시적 타입 변환
1
2
3
4
5
6
7
8
9
10
11
// 문자로 변환 ( 값 + 빈 문자열 )
1 + ""; // "1"
// "+"는 문자열 연결 연산자, 더하기 연산자로 사용할 수 있기 때문에 문자열을 우선으로 생각합니다.
// 하지만 "-", "*", "/"의 경우 피연산자가 문자열이라면 자동으로 숫자로 변환되어 계산됩니다.
// 숫자로 변환 ( +문자 )
+"1.2"; // 1.2
// 불리언으로 변환 ( !!값 )
!!"1"; // true
!!0; // false
📌 let과 const 선언 규칙
- 방법1) 모두
const
로 선언 후 바뀌는 값에만let
선언 - 방법2) 바뀌지 않는 값에만
const
선언
저도 책의 저자와 같이 1번 방식을 더 선호합니다.
📌 스코프
프로그램 소스 코드에서 해당 변수가 정의된 영역을 의미합니다.
let
, const
는 블록 스코프, var
는 함수 스코프를 가집니다.
📌 선언과 타입
자바스크립트는 변수는 타입이 존재하지 않고 어떤 타입이든 넣을 수 있습니다.
좀 바꿔서 말하면 number
타입을 갖는 변수에 string
타입의 값을 넣어도 상관없다는 의미입니다.
하지만 이런 방식은 좋은 프로그래밍 스타일이 아니라서 지양하는게 좋습니다.
📌 가비지 컬렉션
JS
는 접근할 수 없는 값을 알아서 메모리 할당 해제를 시켜줘서 프로그래머가 신경써주지 않아도 됩니다.
위 MDN 링크를 읽어보니 Mark-and-sweep
알고리즘을 이용하기 때문에 순환 참조를 해도 상관없다고 적혀있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 접근할 수 없는 값의 예시
let arr = [1, 2, 3, 4, 5];
// "arr"을 이용한 어떤 특별한 처리를 했고 더 이상 "arr"을 사용하지 않는다고 가정
const sum = arr.reduce((p, c) => p + c);
// 이제 "[1, 2, 3, 4, 5]"라는 값에 접근할 수 있는 방법은 어디에도 없음
// 따라서 가비지 컬렉터가 그것을 인지하고 본인이 원할 때 할당을 해제 시켜서 메모리의 낭비된 공간을 없앰
arr = null;
// 순환 참조 ( mdn 예시 )
// "f()"라는 함수가 끝나도 "x -> y, y -> x를 참조하기 때문에 x와 y에 접근할 수 없더라고 할당 해제 되지 않는다"
// 라고 알고 있었는데 "Mark-and-sweep"알고리즘을 사용하기 때문에 할당 해제 시킨다고 합니다.
function f() {
var x = {};
var y = {};
x.a = y; // x는 y를 참조합니다.
y.a = x; // y는 x를 참조합니다.
return "azerty";
}
f();
📌 래퍼 객체
객체만이 메서드를 가질 수 있습니다.
하지만 기본 null
과 undefined
를 제외한 기본 타입들도 메서드를 사용할 수 있습니다.
그 이유는 래퍼 객체 덕분입니다.
Number
, String
, Object
같이 이미 존재하는 타입의 객체를 래퍼 객체라고 합니다.
1
2
3
4
5
6
7
8
9
// 아래와 같은 코드가 가능한 이유는 래퍼 객체 덕분입니다.
"apple".toLocaleUpperCase(); // "APPLE"
/**
* 1. `new String("apple")` 으로 String 객체 생성
* 2. String 객체가 가지는 `toLocaleUpperCase()`라는 메서드 사용 ( 콘솔에 `String.prototype`을 입력해보면 어떤 메서드들이 있는지 알 수 있다. )
* 3. 객체는 버리고 기본 타입으로 변경 ( 객체가 기본 타입보단 더 무겁기 때문에 버림 )
* ( 3-1. 버려진 객체는 누구도 접근할 수 없으므로 가비지 컬렉터에 의해서 메모리 할당 해제가 된다. )
*/
📌 작성하지 않는 주제
Math
- 날짜와 시간 ( 11장에서 설명한다고 함 )
- 정규표현식 ( 11장에서 설명한다고 함 )
- 일치 연산자(
==
)와 동등 연산자(===
) - 분해 할당(
destructuring
)… 이거는 배열이랑 객체의 개념이 많이 필요해서 생략
❓ 의문
1. 호이스팅
책에서는 호이스팅을 var
의 특징이고 let
의 결점이라고 이해했는데 제가 공부했던 바로는 let
/const
처럼 오류가 나는 것이 더 합리적이라고 생각합니다.
코드는 위에서 아래, 좌측에서 우측으로 읽는데 선언을 하기전에 사용하면 오류가 나는게 조금 더 사람이 이해하기 쉽다고 생각합니다.
그래서 변수는 사용하기전에 선언하는 것이 좋고, 함수는 함수 표현식을 이용해서 선언해서 선언 전의 접근을 막는 것이 좋다고 생각합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// console.log(number); // ReferenceError: number is not defined
// console.log(digit); // ReferenceError: digit is not defined
func1() // "func1()"
// func2() // ReferenceError: func2 is not defined
const number = 10;
let digit = 20;
function func1() {
console.log("func1()");
}
// 함수 표현식
const func2 = function() {
console.log("func2()");
}