이터러블
이터러블 프로토콜을 준수한 객체
즉, Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속받은 객체
✅ 이터러블 프로토콜 (Iterable Protocol)
ES6 이전의 순회 가능한 데이터 컬렉션(배열, 문자열, 유사 배열 객체, DOM 컬렉션 등)은 통일된 규약 없이 나름의 구조를 가지고 for 문, for...in문, forEach 메서드 등 다양한 방법으로 순회할 수 있었다.
ES6에서는 순회 가능한 데이터 컬렉션을 이터레이션 프로토콜을 준수하는 이터러블로 통일해 for..of문, 스프레드 문법, 배열 디스트럭처링 할당의 대상으로 사용하도록 일원화했다.
- Symbol.iterator 메서드를 직접 구현하지 않거나 상속받지 않은 일반 객체는 이터러블 프로토콜을 준수한 이터러블이 아니다.
- 이러한 일반 객체는 for.. of 문 순회, 스프레드 문법, 배열 디스트럭처링 할당의 대상이 아니다.
for ... of 문
이터러블을 순회하면서 이터러블의 요소를 변수에 할당한다.
다음과 같이 내부적으로 동작한다.
- for ... of 문은 내부적으로 이터레이터의 next 메서드를 호출해 이터러블을 순회한다.
- next 메서드가 반환한 이터레이터 리절트 객체의 value 프로터피 값을 for ... of 문의 변수에 할당한다.
- 이터레이터 리절트 객체의 done 프로퍼티 값이 false면 이터러블의 순회를 계속하고 true이면 이터러블의 순회를 중단한다.
✅ 이터레이터(Iterator)
이터러블의 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환한다. 이터러블의 Symbol.iterator 메서드가 반환한 이터레이터는 next 메서드를 갖는다. next 메서드를 호출하면 이터러블을 순차적으로 한 단계씩 순회하며 순회 결과를 나타내는 iterator result object(이터레이터 리절트 객체)를 반환한다.
iterator result object의 value 프로퍼티는 현재 순회 중인 이터러블의 값을 나타낸다.
iterator result object의 done 프로퍼티는 이터러블의 순회 완료 여부를 나타낸다.
const iterable = [1, 2, 3];
// 이터러블의 Symbol.iterator 메서드를 호출하여 이터레이터를 생성한다.
const iterator = iterable[Symbol.iterator]();
for (;;) {
// 이터레이터의 next 메서드를 호출하여 이터러블을 순회한다.
// 이때 next 메서드는 이터레이터 리절트 객체를 반환한다.
const res = iterator.next();
// next 메서드가 반환한 이터레이터 리절트 객체의 done 프로퍼티 값이 true이면 이터러블 순회를 중단한다.
if (res.done) break;
// 이터레이터 리절트 객체의 value 프로퍼티 값을 item 변수에 할당한다.
const item = res.value;
console.log(item); // 1 2 3
}
이터레이션 프로토콜의 필요성
다양한 데이터 공급자가 이터레이션 프로토콜을 준수하도록 규정해 데이터 소비자는 이터레이션 프로토콜만 지원하도록 구현하면 된다.
이터레이션 프로토콜은 다양한 데이터 공급자가 하나의 순회 방식을 갖도록 규정해 데이터 소비자가 효율적으로 사용할 수 있도록 이 둘을 연결해주는 인터페이스 역할을 한다.
스프레드 문법(...)
ES6에 도입된 스프레드 문법은 하나로 뭉쳐 있는 여러 값들의 집합을 펼쳐 개별적인 값들의 목록으로 만든다.
- 스프레드 문법을 사용할 수 있는 대상은 다음과 같다.
- for...of 문으로 순회할 수 있는 이터러블
- Array, String, Map, Set, DOM 컬렉션, arguments 객체
- 함수 호출문의 인수 목록
- 배열 리터럴의 요소 목록
- 객체 리터럴의 프로퍼티 목록
함수 호출문의 인수 목록에서 사용
const arr = [1, 2, 3];
// Math.max(...[1, 2, 3]) -> Math.max(1, 2, 3)
Math.max(...arr); // 3
❗️ 스프레드 문법 vs Rest 파라미터
둘의 형태는 동일하지만 서로 반대의 개념을 갖는다.
▪️ 스프레드 문법: 여러 개의 값이 하나로 뭉쳐져 있는 이터러블을 펼쳐서 개별적인 값의 목록을 만드는 것
▪️ Rest 파라미터: 함수에 전달된 인수들의 목록을 배열로 전달받는 것
배열 리터럴 내부에서 사용하는 경우
- ES5에서는 두 개의 배열을 하나의 배열로 결합하거나 중간에 다른 배열의 요소들을 추가하려면 concat이나 splice 메서드를 사용해야 했다.
- ES5에서는 배열 복사 시 slice 메서드를 사용해야 했다.
- 이터러블을 배열로 변환할 수 있다.
- 스프레드 문법을 사용해 간결하고 가독성 좋게 표현할 수 있다.
// ES5
var arr = [1, 2].concat([3, 4]);
console.log(arr); // [1, 2, 3, 4]
// ES6
const arr = [...[1, 2], ...[3, 4]];
console.log(arr); // [1, 2, 3, 4]
// ES5
var origin = [1, 2];
var copy = origin.slice();
console.log(copy); // [1, 2]
console.log(copy === origin); // false
// ES6
var origin = [1, 2];
var copy = [...origin];
console.log(copy); // [1, 2]
console.log(copy === origin); // false
- 단, 스프레드 문법은 배열 복사 시 1 depth에서만 효과적으로 동작한다.
// 이터러블을 배열로 변환
function sum() {
return [...arguments].reduce((pre, cur) => pre + cur, 0);
}
console.log(sum(1, 2, 3)); // 6
// 위 예제보다 나은 방법: Rest 파라미터를 사용하는 것
const sum = (...args) => args.reduce((pre, cur) => pre + cur, 0);
console.log(sum(1, 2, 3)); // 6
객체 리터럴 내부에서 사용
- 스프레드 프로퍼티 제안: 일반 객체를 대상으로 스프레드 문법의 사용을 허용한다.
- 여러 개의 객체를 병합하거나 특정 프로퍼티를 변경 또는 추가하는 것이 편리하다.
// 스프레드 프로퍼티
// 객체 복사
const obj = { x: 1, y: 2}
const copy = { ...obj };
console.log(copy); // { x: 1, y: 2}
console.log(obj === copy); // false
// 객체 병합
const merged = { x: 1, y: 2, ... { a: 3, b: 4 } };
console.log(merged); // { x: 1, y: 2, a: 3, b: 4 }
// 특정 프로퍼티 변경
const changed = { ...{ x: 1, y: 2 }, { y: 100 });
console.log(changed); // { x: 1, y: 100 }
// 프로퍼티 추가
const added = { ...{ x: 1, y: 2 }, z: 0 };
console.log(added); // { x: 1, y: 2, z: 0 }
------------------------------------------------------------------------------------
// Object.assign()
// 객체 병합. 프로퍼티가 중복되는 경우 뒤에 위치한 프로퍼티가 우선권을 갖는다.
const merged = Object.assign({}, { x: 1, y: 2 }, { y: 10, z: 3 });
console.log(merged); // { x: 1, y: 10, z: 3 }
// 특정 프로퍼티 변경
const changed = Object.assign({}, { x: 1, y: 2 }, { y: 100 });
console.log(changed); // { x: 1, y: 100 }
// 프로퍼티 추가
const added = Object.assign({}, { x: 1, y: 2 }, { z: 0 });
console.log(added); // { x: 1, y: 2, z: 0 }
구조 분해 할당 (Destructuring Assignment)
구조화된 배열과 같은 이터러블 또는 객체를 destructuring(비구조화, 구조 파괴)하여 1개 이상의 변수에 개별적으로 할당하는 것
- 필요한 값만 추출하여 변수에 할당할 때 유용하다.
배열 구조 분해 할당
- 할당 연산자 좌변에는 값을 할당받을 변수를 배열 리터럴 형태로 선언한다.
- 우변에는 이터러블을 할당한다. (할당하지 않으면 에러 발생)
const arr = [1, 2, 3];
const [one, two, three] = arr;
console.log(one, two, three); // 1 2 3
const [x, y]; // Uncaught SyntaxError: Missing initializer in destructuring declaration
const [a, b] = {}; // Uncaught TypeError: {} is not iterable
- 변수의 개수와 이터러블의 요소 개수가 반드시 일치하지 않아도 된다.
- 변수에 기본값을 설정할 수 있다.
const [c, d] = [1];
console.log(c, d); // 1 undefined
const [g, , h] = [1, 2, 3];
console.log(g, h); // 1 3
// 기본값 설정
const [a, b, c = 3] = [1, 2];
console.log(a, b, c); // 1 2 3
- Rest 파라미터와 유사하게 Rest 요소를 사용할 수 있다.
- 반드시 가장 마지막에 위치한다.
const [x, ...y] = [1, 2, 3];
console.log(x, y); // 1 [2, 3];
객체 구조 분해 할당
- 할당 연산자 좌변에 프로퍼티 값을 할당받을 변수를 객체 리터럴 형태로 선언한다.
- 우변에는 객체 또는 객체로 평가될 표현식을 할당한다. (할당하지 않으면 에러 발생)
- 변수 선언 순서는 상관 없고, 선언된 변수 이름과 프로퍼티 키가 일치하면 할당된다.
const user = {firstName: 'Gildong', lastName: 'Hong' };
const { firstName, lastName } = user;
console.log(firstName, lastName); // Gildong Hong
const { firstName, lastName }; // Uncaught SyntaxError: Missing initializer in destructuring declaration
const { firstName, lastName } = null; // Uncaught TypeError: Cannot destructure property 'firstName' of 'null' as it is null.
- 객체의 프로퍼티 키와 다른 변수 이름으로 프로퍼티 값을 할당받기 위해서는 다음과 같이 변수를 선언한다.
const user = { firstName: 'Gildong', lastName: 'Hong' };
// 프로퍼티 키가 lastName인 프로퍼티 값을 ln에 할당한다.
// 프로퍼티 키가 firstName인 프로퍼티 값을 fn에 할당한다.
const { lastName: ln, firstName: fn } = user;
console.log(fn, ln); // Gildong Hong
- 할당을 위한 변수에 기본값을 설정할 수 있다.
const { firstName = 'Gildong', lastName } = { lastName: 'Hong' };
console.log(firstName, lastName); // Gildong Hong
- 객체를 인수로 전달받는 함수의 매개변수에 구조 분해 할당을 사용하면 가독성 좋게 표현할 수 있다.
function printTodo({ content, completed }) {
console.log(`할일 ${content}은 ${completed ? '완료' : '비완료'} 상태입니다.`);
}
printTodo({ id: 1, content: 'HTML', completed: true }); // 할일 HTML은 완료 상태입니다.
- 배열의 요소가 객체인 경우 배열 구조 분해 할당과 객체 구조 분해 할당을 혼용할 수 있다.
const todos = [
{ id: 1, content: 'HTML', completed: true },
{ id: 2, content: 'CSS', completed: false },
{ id: 3, content: 'JS', completed: false }
];
// todos 배열의 두 번째 요소인 객체로부터 id 프로퍼티만 추출한다.
const [, { id }] = todos;
console.log(id); // 2
- 중첩 객체
const user = {
name: 'Lee',
address: {
zipCode: '03068',
city: 'Seoul',
}
};
// address 프로퍼티 키로 객체를 추출하고 이 객체의 city 프로퍼티 키로 값을 추출한다.
const { address: { city } } = user;
console.log(city); // 'Seoul'
참고
'JavaScript > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글
19. 클로저 (0) | 2022.10.12 |
---|---|
28. 브라우저 렌더링 과정 (0) | 2022.09.26 |
26. 표준 빌트인 객체 - String (0) | 2022.08.19 |
25. 표준 빌트인 객체 - Date (0) | 2022.08.10 |
24. 표준 빌트인 객체 - Math (0) | 2022.08.10 |