원시값
- 원시형 값입니다.
- 원시형의 종류는
string
number
bigint
boolean
symbol
null
undefined
총 7가지 입니다.
객체
- 프로퍼티에 다양한 종류의 값을 저장할 수 있습니다.
- 중괄호를 사용해 만들 수 있습니다. 함수도 객체의 일종입니다.
객체의 장점 중 하나는 함수를 프로퍼티로 저장할 수 있다는 것입니다. 그래서 자바스크립트는 날짜, 오류, HTML 요소 등을 다룰 수 있게 해주는 다양한 내장 객체를 제공합니다.
하지만 이런 기능을 사용하면 시스템 자원이 많이 소모된다는 단점이 있습니다. 객체는 원시값보다 무겁고, 내부 구조를 유지하기 위해 추가 자원을 사용하기 때문입니다.
- 문자열이나 숫자와 같은 원시값을 다루어야 하는 작업이 많은데, 메서드를 사용하면 작업을 수월하게 할 수 있을 것 같다는 생각이 듭니다.
- 그런데 원시값은 가능한 한 빠르고 가벼워야 합니다.
원시형 타입을 (일시적으로) 객체화 시켜주는 객체형 데이터 타입을 의미한다.
숫자, 문자열, Boolean 등 원시타입의 프로퍼티 or 메서드에 접근할 때 생성되는 임시 객체
let str = "Hello"
alert(str.toUpperCase()); // HELLO
- 문자열
str
은 원시값이므로 원시값의 프로퍼티(toUpperCase)에 접근하는 순간 특별한 객체가 만들어집니다. 이 객체는 문자열의 값을 알고 있고,toUpperCase()
와 같은 유용한 메서드를 가지고 있습니다. - 메서드가 실행되고, 새로운 문자열이 반환됩니다(
alert
창에 이 문자열이 출력됩니다). - 특별한 객체는 파괴되고, 원시값
str
만 남습니다.
null/undefined
는 메서드가 없습니다.
모던 자바스크립트는 숫자를 나타내는 두 가지 자료형을 지원합니다.
배정밀도 부동소수점 숫자로 알려진 64비트 형식의 IEEE-754에 저장됩니다.
임의의 길이를 가진 정수는 BigInt 숫자로 나타낼 수 있습니다.
e
0x
, 0b
, 0o
Math.floor()
버림
Math.ceil
올림
Math.round
반올림
Math.trunc() ⇒ (IE에서는 지원하지 않는다.)
소수부를 무시
숫자는 내부적으로 64비트 형식으로 표현되기 때문에 숫자를 저장하려면 정확히 64비트가 필요합니다. 64비트 중 52비트는 숫자를 저장하는 데 사용되고, 11비트는 소수점 위치를(정수는 0), 1비트는 부호를 저장하는데 사용됩니다.
그런데 숫자가 너무 커지면 64비트 공간이 넘쳐서 Infinity로 처리됩니다.
정밀도 손실(loss of precision)
alert(0.1 + 0.2); // 0.30000000000000004
숫자는 0과 1로 이루어진 이진수로 변환되어 연속된 메모리 공간에 저장됩니다. 그런데 10진법을 사용하면 쉽게 표현할 수 있는 0.1
, 0.2
같은 분수는 이진법으로 표현하면 무한 소수가 됩니다.
2진법을 사용해서 0.1 또는 0.2를 정확하게 저장하는 방법은 없습니다.
문제를 해결하는 방법은 toFixed(n) 메서드를 사용해 어림수를 만드는 것입니다.
let sum = 0.1 + 0.2;
alert(sum.toFixed(2)); // 0.30
toFixed
는 항상 문자열을 반환한다는 점에 유의해야 합니다. 문자형으로 바꾸니 숫자를 다시 숫자형으로 강제 변환하려면 단항 덧셈 연산자를 사용하면 됩니다.
let sum = 0.1 + 0.2;
alert(+sum.toFixed(2)); // 0.3
Infinity
와-Infinity
- 그 어떤 숫자보다 큰 혹은 작은 특수 숫자 값NaN
- 에러를 나타내는 값
isNaN(value) - 인수를 숫자로 변환한 다음 NaN
인지 테스트함
굳이 이 함수가 필요할까요? === NaN
비교를 하면 되지 않을까? 라는 생각이 들 수 있습니다. NaN
는 NaN
자기 자신을 포함하여 그 어떤 값과도 같지 않다는 점에서 매우 독특합니다.
alert(NaN === NaN); // false
isFinite(value) - 인수를 숫자로 변환하고 변환한 숫자가 NaN/Infinity/-Infinity 가 아닌 일반 숫자인 경우 true를 반환합니다.
💡 **Object.is** 와 비교하기 `===` 처럼 값을 비교할 때 사용되는 특별한 내장 메서드인데, 아래와 같은 두 가지 에지 케이스에선 `===` 보다 좀 더 신뢰할 만한 결과를 보여줍니다.NaN
을 대상으로 비교할 때: Object.is(NaN, NaN) === true 임.0
과-0
이 다르게 취급되어야 할 때:Object.is(0, -0) === false
임. 숫자를 나타내는 비트가 모두 0이더라도 부호를 나타내는 비트는 다르므로0
과-0
은 사실 다른 값이긴 합니다.
단항 덧셈 연산자 +
또는 Number()
를 사용하여 숫자형으로 변형할 때 적용되는 규칙은 꽤 엄격합니다. 피연산자가 숫자가 아니면 형 변환이 실패합니다.
실무에선 CSS 등에서 100px
12pt
와 같이 숫자와 단위를 함께 스는 경우가 흔합니다. 내장 함수 parseInt
와 parseFloat
는 이런 경우를 위해 만들어졌습니다.
두 함수는 불가능할 때까지 문자열에서 숫자를 ‘읽습니다’.
Math.random() - 0과 1 사이의 난수를 반환합니다.(1은 제외)
Math.max(a, b, c…) / Math.min(a, b, c … )
인 수 중 최대/ 최솟값을 반환합니다.
const arr = [1,3,5,6,7];
Math.max(...arr);
Math.pow(n, power)
n
을 power번 거듭제곱한 값을 반환합니다.
console.log(Math.pow(2, 10)); // 2의 10제곱 = 1024
자바스크립트엔 글자 하나만 저장할 수 있는 별도의 자료형이 없습니다. 텍스트 형식의 데이터는 길이에 상관없이 문자열 형태로 저장됩니다.
자바스크립트에서 문자열은 페이지 이코딩 방식과 상관없이 항상 UTF-16 형식을 따릅니다.
작은 따옴표나 큰따옴표, 백틱으로 감쌀 수 있습니다.
${…} 로 감싸고 이를 백틱으로 감싼 문자열 중간에 넣어주면 해당 표현식을 문자열 중간에 쉽게 삽입할 수 있습니다. 이를 템플릿 리터럴이라고 부릅니다.
\n
를 사용하면 작은 따옴표나 큰따옴표로도 여러 줄 문자열을 만들 수 있습니다.
length
프로퍼티엔 문자열의 길이가 저장됩니다.
str.indexOf
includes
startWith, endsWith - 문자열이 특정 문자열로 시작하는지(startWith) 여부와 특정 문자열로 끝나는지 (end with) 여부를 확인할 때 사용할 수 있습니다.
substring, substr, slice를 통해 부분 문자열을 추출할 수 있습니다.
💡 **어떤 메서드를 선택해야 하나요?**모두 사용해도 괜찮습니다. 그런데 substr
에는 단점이 하나 있습니다. substr
는 코어 자바스크립트 명세서(ECMA-262 – 옮긴이)가 아닌, 구식 스크립트에 대응하기 위해 남겨 둔 브라우저 전용 기능들을 명시해 놓은 부록 B(Annex B)에 정의되어있습니다. 거의 모든 곳에서 이 메서드가 동작하긴 하지만 브라우저 이외의 호스트 환경에서는 제대로 동작하지 않을 수 있습니다.
남은 두 메서드 중 slice
는 음수 인수를 허용한다는 측면에서 substring
보다 좀 더 유연합니다. 메서드 이름도 더 짧죠. 따라서 세 메서드 중 slice
만 외워놓고 사용해도 충분할 것 같습니다.
- 소문자는 대문자보다 항상 큽니다.
- 발음 구별 기호가 붙는 문자는 알파벳 순서 기준을 따르지 않습니다.
순서가 있는 컬렉션을 다뤄야 할 때 객체를 사용하면 순서와 관련된 메서드가 없어 그다지 편리하지 않습니다. 객체는 태생이 순서를 고려하지 않고 만들어진 자료구조이기 때문에 객체를 이용하면 새로운 프로퍼티를 기존 프로퍼티 ‘사이에’ 끼워 넣는 것도 불가능합니다.
이럴 땐 순서가 있는 컬렉션을 저장할 때 쓰는 자료구조인 배열
을 사용할 수 있습니다.
큐(queue)는 배열을 사용해 만들 수 있는 대표적인 자료구조로, 배열과 마찬가지로 순서가 있는 컬렉션을 저장하는 데 사용합니다. 큐에서 사용하는 주요 연산은 아래와 같습니다.
- push - 맨 끝에 요소를 추가합니다.
- shift - 제일 앞 요소를 꺼내 제거한 후 남아있는 요소들을 앞으로 밀어줍니다.
배열은 큐 이외에 스택이라 불리는 자료구조를 구현할 때도 쓰입니다.
- push - 요소를 스택 끝에 집어넣습니다.
- pop - 스택 끝 요소를 추출합니다.
배열에 적용할 수 있는 또 다른 순회 문법으로는 for…of
가 있다.
배열은 객체형에 속하므로 for...in
을 사용하는 것도 가능합니다.
let fruits = ["사과", "오렌지", "자두"];
// 배열 요소를 대상으로 반복 작업을 수행합니다.
for (let fruit of fruits) {
alert( fruit );
}
- push
- pop
- shfit
- unshift
splice
slice
concat
forEach
indexOf, lastIndexOf, includes
filter
map
sort(fn)
localeCompare
reverse
split, join
reduce, reduceRight
반복 가능한 객체는 배열을 일반화한 객체입니다. 이터러블이라는 개념을 사용하면 어떤 객체에든 for…of 반복문을 적용할 수 있습니다.
- 이터러블엔 메서드 Symbol.iterator가 반드시 구현되어 있어야 합니다.
- obj[Symbol.iterator]의 결과는 이터레이터라고 부릅니다. 이터레이터는 이어지는 반복 과정을 처리합니다.
- 이터레이터엔 객체 {done: Boolean, value: any}을 반환하는 메서드 next()가 반드시 구현되어 있어야 합니다. 여기서 done:true은 반복이 끝났음을 의미하고 그렇지 않은 경우엔 value가 다음 값이 됩니다.
- 메서드 Symbol.iterator는 for..of에 의해 자동으로 호출되는데, 개발자가 명시적으로 호출하는 것도 가능합니다.
- 문자열이나 배열 같은 내장 이터러블에도 Symbol.iterator가 구현되어 있습니다.
- 문자열 이터레이터는 서로게이트 쌍을 지원합니다
- 맵은 키가 있는 데이터를 저장한다는 점에서 객체와 유사합니다. 다만, 맵은 키에 다양한 자료형을 허용한다는 점에서 차이가 있습니다.
new Map()
– 맵을 만듭니다.map.set(key, value)
–key
를 이용해value
를 저장합니다.map.get(key)
–key
에 해당하는 값을 반환합니다.key
가 존재하지 않으면undefined
를 반환합니다.map.has(key)
–key
가 존재하면true
, 존재하지 않으면false
를 반환합니다.map.delete(key)
–key
에 해당하는 값을 삭제합니다.map.clear()
– 맵 안의 모든 요소를 제거합니다.map.size
– 요소의 개수를 반환합니다.
let map = new Map();
map.set('1', 'str1'); // 문자형 키
map.set(1, 'num1'); // 숫자형 키
map.set(true, 'bool1'); // 불린형 키
// 객체는 키를 문자형으로 변환한다는 걸 기억하고 계신가요?
// 맵은 키의 타입을 변환시키지 않고 그대로 유지합니다. 따라서 아래의 코드는 출력되는 값이 다릅니다.
alert( map.get(1) ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3
map.keys()
– 각 요소의 키를 모은 반복 가능한(iterable, 이터러블) 객체를 반환합니다.map.values()
– 각 요소의 값을 모은 이터러블 객체를 반환합니다.map.entries()
– 요소의[키, 값]
을 한 쌍으로 하는 이터러블 객체를 반환합니다. 이 이터러블 객체는for..of
반복문의 기초로 쓰입니다.
셋(Set)
은 중복을 허용하지 않는 값을 모아놓은 특별한 컬렉션입니다. 셋에 키가 없는 값이 저장됩니다.
주요 메서드는 다음과 같습니다.
new Set(iterable)
– 셋을 만듭니다.이터러블
객체를 전달받으면(대개 배열을 전달받음) 그 안의 값을 복사해 셋에 넣어줍니다.set.add(value)
– 값을 추가하고 셋 자신을 반환합니다.set.delete(value)
– 값을 제거합니다. 호출 시점에 셋 내에 값이 있어서 제거에 성공하면true
, 아니면false
를 반환합니다.set.has(value)
– 셋 내에 값이 존재하면true
, 아니면false
를 반환합니다.set.clear()
– 셋을 비웁니다.set.size
– 셋에 몇 개의 값이 있는지 세줍니다.
let set = new Set();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
// 어떤 고객(john, mary)은 여러 번 방문할 수 있습니다.
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);
// 셋에는 유일무이한 값만 저장됩니다.
alert( set.size ); // 3
for (let user of set) {
alert(user.name); // // John, Pete, Mary 순으로 출력됩니다.
}
set.keys()
– 셋 내의 모든 값을 포함하는 이터러블 객체를 반환합니다.set.values()
–set.keys
와 동일한 작업을 합니다.맵
과의 호환성을 위해 만들어진 메서드입니다.set.entries()
– 셋 내의 각 값을 이용해 만든[value, value]
배열을 포함하는 이터러블 객체를 반환합니다.맵
과의 호환성을 위해 만들어졌습니다.
맵에서 객체를 키로 사용한 경우 역시, 맵이 메모리에 있는 한 객체도 메모리에 남습니다. 가비지 컬렉터의 대상이 되지 않죠.
이런 관점에서 위크맵은 일반 맵과 전혀 다른 양상을 보입니다. 위크맵을 사용하면 키로 쓰인 객체가 가비지 컬렉션의 대상이 됩니다.
위크맵
의 키가 반드시 객체여야 한다는 점입니다. 원시값은 위크맵의 키가 될 수 없습니다.
let weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, "ok"); //정상적으로 동작합니다(객체 키).
// 문자열("test")은 키로 사용할 수 없습니다.
weakMap.set("test", "Whoops"); // Error: Invalid value used as weak map key
위크맵의 키로 사용된 객체를 참조하는 것이 아무것도 없다면 해당 객체는 메모리와 위크맵에서 자동으로 삭제됩니다.
let john = { name: "John" };
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; // 참조를 덮어씀
// john을 나타내는 객체는 이제 메모리에서 지워집니다!
weakMap.get(key)
weakMap.set(key, value)
weakMap.delete(key)
weakMap.has(key)
위크맵
은 부차적인 데이터를 저장할 곳이 필요할 때 그 진가를 발휘합니다.
위크맵
에 원하는 데이터를 저장하고, 이때 키는 객체를 사용하면 됩니다. 이렇게 하면 객체가 가비지 컬렉션의 대상이 될 때, 데이터도 함께 사라지게 됩니다.
weakMap.set(john, "비밀문서");
// 📁 visitsCount.js
let visitsCountMap = new Map(); // 맵에 사용자의 방문 횟수를 저장함
// 사용자가 방문하면 방문 횟수를 늘려줍니다.
function countUser(user) {
let count = visitsCountMap.get(user) || 0;
visitsCountMap.set(user, count + 1);
}
위크맵은 캐싱(caching)이 필요할 때 유용합니다. 캐싱은 시간이 오래 걸리는 작업의 결과를 저장해서 연산 시간과 비용을 절약해주는 기법입니다. 동일한 함수를 여러 번 호출해야 할 때, 최초 호출 시 반환된 값을 어딘가에 저장해 놓았다가 그다음엔 함수를 호출하는 대신 저장된 값을 사용하는 게 캐싱의 실례입니다.
// 📁 cache.js
let cache = new WeakMap();
// 연산을 수행하고 그 결과를 위크맵에 저장합니다.
function process(obj) {
if (!cache.has(obj)) {
let result = /* 연산 수행 */ obj;
cache.set(obj, result);
}
return cache.get(obj);
}
// 📁 main.js
let obj = {/* ... 객체 ... */};
let result1 = process(obj);
let result2 = process(obj);
// 객체가 쓸모없어지면 아래와 같이 null로 덮어씁니다.
obj = null;
// 이 예시에선 맵을 사용한 예시처럼 cache.size를 사용할 수 없습니다.
// 하지만 obj가 가비지 컬렉션의 대상이 되므로, 캐싱된 데이터 역시 메모리에서 삭제될 겁니다.
// 삭제가 진행되면 cache엔 그 어떤 요소도 남아있지 않을겁니다.
위크셋
은셋
과 유사한데, 객체만 저장할 수 있다는 점이 다릅니다. 원시값은 저장할 수 없습니다.- 셋 안의 객체는 도달 가능할 때만 메모리에서 유지됩니다.
셋
과 마찬가지로위크셋
이 지원하는 메서드는 단출합니다.add
,has
,delete
를 사용할 수 있고,size
,keys()
나 반복 작업 관련 메서드는 사용할 수 없습니다.
let visitedSet = new WeakSet();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
visitedSet.add(john); // John이 사이트를 방문합니다.
visitedSet.add(pete); // 이어서 Pete가 사이트를 방문합니다.
visitedSet.add(john); // 이어서 John이 다시 사이트를 방문합니다.
// visitedSet엔 두 명의 사용자가 저장될 겁니다.
// John의 방문 여부를 확인해보겠습니다.
alert(visitedSet.has(john)); // true
// Mary의 방문 여부를 확인해보겠습니다.
alert(visitedSet.has(mary)); // false
john = null;
// visitedSet에서 john을 나타내는 객체가 자동으로 삭제됩니다.
- Object.keys(obj) - 객체의 키만 담은 배열을 반환합니다.
- Object.values(obj) - 객체의 값만 담은 배열을 반환합니다.
- Object.entries(obj) - [키, 값] 쌍을 담은 배열을 반환합니다.
개발을 하다 보면 함수에 객체나 배열을 전달해야 하는 경우가 생기곤 합니다. 가끔은 객체나 배열에 저장된 데이터 전체가 아닌 일부만 필요한 경우가 생기기도 하죠.
이럴 때 객체나 배열을 변수로 '분해’할 수 있게 해주는 특별한 문법인 구조 분해 할당(destructuring assignment) 을 사용할 수 있습니다. 이 외에도 함수의 매개변수가 많거나 매개변수 기본값이 필요한 경우 등에서 구조 분해(destructuring)는 그 진가를 발휘합니다.
// 이름과 성을 요소로 가진 배열
let arr = ["Bora", "Lee"]
// 구조 분해 할당을 이용해
// firstName엔 arr[0]을
// surname엔 arr[1]을 할당하였습니다.
let [firstName, surname] = arr;
alert(firstName); // Bora
alert(surname); // Lee
쉼표를 사용하여 요소 무시하기
- 쉼표를 사용하면 필요하지 않은 배열 요소를 버릴 수 있습니다.
let [firstName, , title] = ['Julius', 'Caesar', 'Consul'];
alert(title) // Consul
‘…’ 로 나머지 요소 가져오기
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert(name1); // Julius
alert(name2); // Caesar
// `rest`는 배열입니다.
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
복잡한 객체를 다루고 있다고 할 때, 네트워크를 통해 객체를 어딘가에 보내거나 로깅 목적으로 객체를 출력해야 한다면 객체를 문자열로 전환해야 한다.
이때 전환된 문자열엔 원하는 정보가 이 있는 객체 프로퍼티 모두가 포함되어야만 한다. 이를 해결하기 위해 다양한 메서드가 있다.
JSON은 값이나 객체를 나타내주는 범용 포맷이다. JSON은 본래 자바스크립트에서 사용할 목적으로 만들어진 포맷입니다. 다른 언어에서도 JSON을 충분히 다룰 수 있어서, JSON을 데이터 교환 목적으로 사용하는 경우가 많다.
- JSON.stringify - 객체를 JSON으로 바꿔줍니다.
- JSON.parse - JSON을 객체로 바꿔줍니다.
let student = {
name: 'John',
age: 30,
isAdmin: false,
courses: ['html', 'css', 'js'],
wife: null
};
let json = JSON.stringify(student);
alert(typeof json); // 문자열이네요!
alert(json);
/* JSON으로 인코딩된 객체:
{
"name": "John",
"age": 30,
"isAdmin": false,
"courses": ["html", "css", "js"],
"wife": null
}
*/
JSON으로 인코딩된 객체는 일반 객체와 다른 특징을 보인다.
- 문자열은 큰따옴표로 감싸야 한다. JSON에선 작은 따옴표나 백틱을 사용할 수 없습니다.
- 객체 프로퍼티 이름은 큰따옴표로 감싸야 합니다.
JSON.stringify
는 객체뿐만 아니라 원시값에도 적용할 수 있습니다.
주의해야 할 점으로는 순환 참조가 있으면 원하는 대로 객체를 문자열로 바꾸는 게 불가능합니다.
let json = JSON.stringify(value, [replacer, space]);
replacer
JSON으로 인코딩 하길 원하는 프로퍼티가 담긴 배열. 또는 매핑 함수 function(key, value)
replacer 함수를 사용하면 중첩 객체 등을 포함한 객체 전체에서 원하는 프로퍼티만 선택해 직렬화 할 수 있습니다.