
반복 가능한 객체 즉, 배열을 일반화한 객체
let range = {
from: 1,
to: 5
};
// 아래와 같이 for..of가 동작할 수 있도록 하는 게 목표
// for(let num of range) ... num=1,2,3,4,5
위 객체를 이터러블 객체로 만들기
객체에 Symbol.iterator(특수 내장 심볼)라는 메서드를 추가
done=true는 반복이 종료되었음을 의미let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
this.current = this.from;
return this;
},
next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}
❗ 두 개의 for..of 반복문을 하나의 객체에 동시에 사용할 수는 없다. 이터레이터(객체 자신)가 하나뿐이어서 두 반복문이 반복 상태를 공유하기 때문. 그러나 동시에 두 개의 for..of를 사용하는 것은 비동기 처리에서도 흔한 케이스는 아니므로 괜찮다.
무한개의 이터레이터
range.to에Infinity를 할당하면range가 무한대가 되므로 무수히 많은 의사 난수를 생성하는 이터러블 객체를 만드는 것이 가능하다.
배열, 문자열은 이터러블.
for (let char of "test") {
// 글자 하나당 한 번 실행됩니다(4회 호출).
alert( char ); // t, e, s, t가 차례대로 출력됨
}
let str = "Hello";
// for..of를 사용한 것과 동일한 작업을 합니다.
// for (let char of str) alert(char);
let iterator = str[Symbol.iterator]();
while (true) {
let result = iterator.next();
if (result.done) break;
alert(result.value); // 글자가 하나씩 출력됩니다.
}
이터레이터를 명시적으로 호출하는 경우는 거의 없는데, 이 방법을 사용하면 for..of를 사용하는 것보다 반복 과정을 더 잘 통제할 수 있다는 장점이 있다. 예를 들어 반복 사이에 다른 작업을 끼워 넣는다던지 반복 과정을 여러 개로 쪼개는 것이 가능!
이터러블 : Symbol.iterator가 구현된 객체
유사배열 : 인덱스와 length프로퍼티가 있어서 배열처럼 보이는 객체
위에서 봤던 예시의 range는 이터러블 객체이지만, 인덱스도 없고 length 프로퍼티도 없으므로 유사 배열 객체가 ❌
아래 예시의 객체는 유사 배열 객체이긴하지만 이터러블 객체가 아니다.
let arrayLike = { // 인덱스와 length프로퍼티가 있음 => 유사 배열
0: "Hello",
1: "World",
length: 2
};
// Symbol.iterator가 없으므로 에러 발생
for (let item of arrayLike) {}
Array.from : 이터러블이나 유사 배열을 받아 진짜 Array로 만들어준다.
let arrayLike = {
0: "Hello",
1: "World",
length: 2
};
let arr = Array.from(arrayLike);
alert(arr.pop()); // World (메서드가 제대로 동작합니다.)
➕ '매핑(mapping)' 함수를 선택적으로 넘겨줄 수 있다.
매핑 함수 mapFn을 넣어주면 새로운 배열에 obj 요소를 추가하기 전에, 각 요소를 대상으로 mapFn을 적용할 수 있고, 세 번째 인수 thisArg는 각 요소의 this를 지정할 수 있도록 해준다.
Array.from(obj[, mapFn, thisArg])
NaN까지도!)let map = new Map();
map.set('1', 'str1'); // 문자형 키
map.set(1, 'num1'); // 숫자형 키
map.set(true, 'bool1'); // 불린형 키
주요 메서드, 프로퍼티
new Map(): 맵 만들기
map에서는 map[key]와 같이 접근하지 않고 항상 아래 메소드를 이용해야 한다.
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– 요소의 개수를 반환합니다.
(참고 : <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Map"Map - MDN)
Map이 키로 객체를 허용하는 예시
let john = { name: "John" };
// 고객의 가게 방문 횟수를 세본다고 가정해 봅시다.
let visitsCountMap = new Map();
// john을 맵의 키로 사용하겠습니다.
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123
➕ map.set을 호출할 때마다 맵 자신이 반환되므로 체이닝할 수 있다.
map.set('1', 'str1')
.set(1, 'num1')
.set(true, 'bool1');
map.keys() – 각 요소의 키를 모은 반복 가능한(iterable, 이터러블) 객체를 반환합니다.map.values() – 각 요소의 값을 모은 이터러블 객체를 반환합니다.map.entries() – 요소의 [키, 값]을 한 쌍으로 하는 이터러블 객체를 반환합니다. 이 이터러블 객체는 for..of반복문의 기초로 쓰입니다.let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// 키(vegetable)를 대상으로 순회합니다.
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// 값(amount)을 대상으로 순회합니다.
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// [키, 값] 쌍을 대상으로 순회합니다.
for (let entry of recipeMap) { // recipeMap.entries()와 동일합니다.
alert(entry); // cucumber,500 ...
}
*forEach도 사용 가능하다
키-값 쌍인 배열이나 이터러블 객체를 초기화 용도로 맵에 전달해 새로운 맵을 만들 수 있다.
// 각 요소가 [키, 값] 쌍인 배열
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
alert( map.get('1') ); // str1
그런데 평범한 객체를 가지고 맵을 만들고 싶다면?
Object.entries(obj) 활용하기 : 객체의 키-값을 [key,value]로 가진 배열 반환
let obj = {
name: "John",
age: 30
};
let map = new Map(Object.entries(obj)); // [ ["name","John"], ["age", 30] ]으로 반환
alert( map.get('name') ); // John
맵을 객체로 바꾸고 싶다면?
Object.fromEntries 사용하기 : 각 요소가 [키, 값] 쌍인 배열을 객체로 반환
let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);
let obj = Object.fromEntries(map); // 맵을 일반 객체로 변환
// 맵이 객체가 되었습니다!
// obj = { banana: 1, orange: 2, meat: 4 }
alert(obj.orange); // 2
중복을 허용하지 않는 값을 모아놓은 특별한 컬렉션
셋 내에 동일한 값(value)이 있다면 set.add(value)을 아무리 많이 호출하더라도 아무런 반응이 없디!
주요 메서드
new Set(iterable)– 셋을 만듭니다. 이터러블 객체를 전달받으면(대개 배열을 전달받음) 그 안의 값을 복사해 셋에 넣어줍니다.
set.add(value)– 값을 추가하고 셋 자신을 반환합니다.
set.delete(value)– 값을 제거합니다. 호출 시점에 셋 내에 값이 있어서 제거에 성공하면 true, 아니면 false를 반환합니다.
set.has(value)– 셋 내에 값이 존재하면 true, 아니면 false를 반환합니다.
set.clear()– 셋을 비웁니다.
set.size– 셋에 몇 개의 값이 있는지 세줍니다.
for..of나 forEach를 사용하면 셋의 값을 대상으로 반복 작업 수행 가능
let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set) alert(value);
// forEach를 사용해도 동일하게 동작합니다.
set.forEach((value, valueAgain, set) => {
alert(value);
});
keys(), values(), entries()를 사용할 수 있는 자료구조
MapSetArray일반 객체에서의 메서드
Object.keys(obj) – 객체의 키만 담은 배열을 반환합니다.
Object.values(obj) – 객체의 값만 담은 배열을 반환합니다.
Object.entries(obj) – [키, 값] 쌍을 담은 배열을 반환합니다.
Object.values 예시
let user = {
name: "Violet",
age: 30
};
// 값을 순회합니다.
for (let value of Object.values(user)) {
alert(value); // Violet과 30이 연속적으로 출력됨
}
객체에Object.entries와 Object.fromEntries를 순차적으로 적용해서 배열 전용 메서드 사용하기
1. Object.entries(obj)를 사용해 객체의 키-값 쌍이 요소인 배열을 얻습니다.
2. 1에서 만든 배열에 map 등의 배열 전용 메서드를 적용합니다.
3. 2에서 반환된 배열에 Object.fromEntries(array)를 적용해 배열을 다시 객체로 되돌립니다.
let prices = {
banana: 1,
orange: 2,
meat: 4,
};
let doublePrices = Object.fromEntries(
// 객체를 배열로 변환해서 배열 전용 메서드인 map을 적용하고 fromEntries를 사용해 배열을 다시 객체로 되돌립니다.
Object.entries(prices).map(([key, value]) => [key, value * 2])
);
alert(doublePrices.meat); // 8
아래와 같이 배열을 분해해 배열에 접근하지 않고 변수로 값 사용하기
// 이름과 성을 요소로 가진 배열
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", "of the Roman Republic"];
alert( title ); // Consul
➕ 변수 교환하기
let guest = "Jane";
let admin = "Pete";
// 변수 guest엔 Pete, 변수 admin엔 Jane이 저장되도록 값을 교환함
[guest, admin] = [admin, guest];
alert(`${guest} ${admin}`); // Pete Jane(값 교환이 성공적으로 이뤄졌습니다!)
rest 문법
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
할당하고자 하는 변수의 개수가 분해하고자 하는 배열의 길이보다 크더라도 할당할 값이 없으면 undefined로 취급된다.
그리고 =를 이용하면 할당할 값이 없을 때 default value를 설정할 수 있다.
// 기본값 Guest, Anonymous
let [name = "Guest", surname = "Anonymous"] = ["Julius"];
alert(name); // Julius (배열에서 받아온 값)
alert(surname); // Anonymous (기본값)
defalt value에 함수도 가능!!! 유용하게 쓰일 수 있을 것 같다
// name의 prompt만 실행됨
let [surname = prompt('성을 입력하세요.'), name = prompt('이름을 입력하세요.')] = ["김"];
alert(surname); // 김 (배열에서 받아온 값)
alert(name); // prompt에서 받아온 값
let options = {
title: "Menu",
width: 100,
height: 200
};
let {title, width, height} = options;
객체에서 순서는 중요하지 않고 상응하는 변수에 할당된 것을 가져온다.
// let {...} 안의 순서가 바뀌어도 위 코드와 동일하게 동작함
let {height, width, title} = { title: "Menu", height: 200, width: 100 }
마찬가지로 기본값 설정 가능
let {width = 100, height = 200, title} = options;
마찬가지로 ... 나머지 문법도 사용 가능하다.
➕ let으로 새로운 변수를 선언하지 않고 기존에 있던 변수에 할당하고 싶다면
let title, width, height;
// SyntaxError: Unexpected token '=' 이라는 에러가 아랫줄에서 발생합니다.
{title, width, height} = {title: "Menu", width: 200, height: 100};
이렇게 했을 때 { }를 코드 블록으로 인식해서 오류가 나므로
let title, width, height;
// 에러가 발생하지 않습니다.
({title, width, height} = {title: "Menu", width: 200, height: 100});
alert( title ); // Menu
이렇게 ( )로 감싸서 표현식으로 해석하게끔 해주면 된다.
객체나 배열이 다른 객체나 배열을 포함하는 경우
let options = {
size: {
width: 100,
height: 200
},
items: ["Cake", "Donut"],
extra: true
};
// 코드를 여러 줄에 걸쳐 작성해 의도하는 바를 명확히 드러냄
let {
size: { // size는 여기,
width,
height
},
items: [item1, item2], // items는 여기에 할당함
title = "Menu" // 분해하려는 객체에 title 프로퍼티가 없으므로 기본값을 사용함
} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
alert(item1); // Cake
alert(item2); // Donut
객체 프로퍼티가 모두 상응하는 변수에 할당되었고, 유일하게 원래 배열의 특성에 따라 배열만 변수 대신 정보가 그대로 전달된다.
함수의 매개변수에서도 구조분해할당이 가능하다.
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
function showMenu({
title = "Untitled",
width: w = 100, // width는 w에,
height: h = 200, // height는 h에,
items: [item1, item2] // items의 첫 번째 요소는 item1에, 두 번째 요소는 item2에 할당함
}) {
alert( `${title} ${w} ${h}` ); // My Menu 100 200
alert( item1 ); // Item1
alert( item2 ); // Item2
}
showMenu(options);
함수 매개변수를 구조 분해할 때는, 반드시 인수가 전달된다고 가정되고 사용된다는 점에 유의해야 한다. 따라서 인수가 전달되지 않는 경우 에러가 날 수 있다
=>
아래처럼 빈 객체 {}를 인수 전체의 기본값으로 만들면, 인수가 전달되지 않았더라도 어떤 경우든 분해하므로 에러가 발생하지 않는다.
function showMenu({ title = "Menu", width = 100, height = 200 } = {}) {
alert( `${title} ${width} ${height}` );
}
showMenu(); // Menu 100 200