이터러블
ES6에서 도입된 이터레이션 프로토콜은 순회 가능한 자료구조를 위해 미리 정의한 규칙이다. ES6 이전의 배열, 문자열, 유사 배열 객체, DOM 컬렉션 등은 통일된 규약 없이 다양한 방법으로 순회할 수 있는데, ES6에서는 순회 가능한 자료구조를 이터러블로 통일해 for…of문, 스프레드 문법, 디스트럭처링 할당의 대상으로 사용할 수 있도록 일원화했다
const isIterable = v => v !== null && typeof v[Symbol.iterator] === 'function';
// 배열, 문자열, Map, Set 등은 이터러블이다.
isIterable([]); // -> true
isIterable(''); // -> true
isIterable(new Map()); // -> true
isIterable(new Set()); // -> true
isIterable({}); // -> false
// 배열은 이터러블 프로토콜을 준수한 이터러블이다.
const array = [1, 2, 3];
// Symbol.iterator 메서드는 이터레이터를 반환한다.
const iterator = array[Symbol.iterator]();
// Symbol.iterator 메서드가 반환한 이터레이터는 next 메서드를 갖는다.
console.log('next' in iterator); // true
이터러블의 기준은 Symbol.iterator 유무 및 요구사항 충족 여부이며, 반드시 정해져 있지는 않다
const array = [1, 2, 3];
// 배열은 Array.prototype의 Symbol.iterator 메서드를 상속받는 이터러블이다.
console.log(Symbol.iterator in array); // true
// 이터러블인 배열은 for...of 문으로 순회 가능하다.
for (const item of array) {
console.log(item);
}
// 이터러블인 배열은 스프레드 문법의 대상으로 사용할 수 있다.
console.log([...array]); // [1, 2, 3]
// 이터러블인 배열은 배열 디스트럭처링 할당의 대상으로 사용할 수 있다.
const [a, ...rest] = array;
console.log(a, rest); // 1, [2, 3]
이터러블이라면 위와 같이 활용할 수 있다
for (const item of [1, 2, 3]) {
// item 변수에 순차적으로 1, 2, 3이 할당된다.
console.log(item); // 1 2 3
}
for…of문은 내부적으로 이터레이터의 next 메서드를 호출하며 이터러블을 순회한다
// 유사 배열 객체
const arrayLike = {
0: 1,
1: 2,
2: 3,
length: 3
};
// Array.from은 유사 배열 객체 또는 이터러블을 배열로 변환한다
const arr = Array.from(arrayLike);
console.log(arr); // [1, 2, 3]
유사 배열 객체는 이터러블이 아닌 일반 객체이기 때문에 for…of문으로 순회할 수 없다
* Array.from을 사용해 배열로 변환
// 피보나치 수열을 구현한 사용자 정의 이터러블
const fibonacci = {
// Symbol.iterator 메서드를 구현하여 이터러블 프로토콜을 준수한다.
[Symbol.iterator]() {
let [pre, cur] = [0, 1]; // "36.1. 배열 디스트럭처링 할당" 참고
const max = 10; // 수열의 최대값
// Symbol.iterator 메서드는 next 메서드를 소유한 이터레이터를 반환해야 하고
// next 메서드는 이터레이터 리절트 객체를 반환해야 한다.
return {
next() {
[pre, cur] = [cur, pre + cur]; // "36.1. 배열 디스트럭처링 할당" 참고
// 이터레이터 리절트 객체를 반환한다.
return { value: cur, done: cur >= max };
}
};
}
};
// 이터러블인 fibonacci 객체를 순회할 때마다 next 메서드가 호출된다.
for (const num of fibonacci) {
console.log(num); // 1 2 3 5 8
}
사용자 정의 이터러블을 구현할 수도 있다
스프레드 문법
ES6에서 도입된 스프레드 문법 (…)은 하나로 뭉쳐 있는 여러 값들의 집합을 펼쳐서 개별적인 값들의 목록으로 만든다
// ...[1, 2, 3]은 [1, 2, 3]을 개별 요소로 분리한다(→ 1, 2, 3)
console.log(...[1, 2, 3]); // 1 2 3
// 문자열은 이터러블이다.
console.log(...'Hello'); // H e l l o
// Map과 Set은 이터러블이다.
console.log(...new Map([['a', '1'], ['b', '2']])); // [ 'a', '1' ] [ 'b', '2' ]
console.log(...new Set([1, 2, 3])); // 1 2 3
// 이터러블이 아닌 일반 객체는 스프레드 문법의 대상이 될 수 없다.
console.log(...{ a: 1, b: 2 });
// TypeError: Found non-callable @@iterator
적용할 수 있는 대상은 Array, String, Map, Set, DOM 컬렉션, arguments와 같이 for…of 문으로 순회할 수 있는 이터러블에 한정된다
// 스프레드 문법의 결과는 값이 아니다.
const list = ...[1, 2, 3]; // SyntaxError: Unexpected token ...
결과를 값으로 사용할 수 없고, 쉼표로 구분한 값의 목록을 사용하는 문맥에서 사용할 수 있다
* 함수 호출문의 인수 / 배열 리터럴의 요소 / 객체 리터럴의 프로퍼티 등
// Rest 파라미터 args는 함수에 전달된 인수들의 목록을 배열로 전달받는다.
const sum = (...args) => args.reduce((pre, cur) => pre + cur, 0);
console.log(sum(1, 2, 3)); // 6
rest 파라미터는 인수들의 목록을 내부에서 배열로 활용한다
// ES6
const arr1 = [1, 4];
const arr2 = [2, 3];
arr1.splice(1, 0, ...arr2);
console.log(arr1); // [1, 2, 3, 4]
splice에 활용해줄 수도 있다
// ES6
const origin = [1, 2];
// var copy = origin.slice();
const copy = [...origin];
console.log(copy); // [1, 2]
console.log(copy === origin); // false
배열 복사 목적으로 slice()보다 스프레드 문법이 가독성이 좋다
// 객체 병합. 프로퍼티가 중복되는 경우, 뒤에 위치한 프로퍼티가 우선권을 갖는다.
const merged = { ...{ x: 1, y: 2 }, ...{ y: 10, z: 3 } };
console.log(merged); // { x: 1, y: 10, z: 3 }
// 특정 프로퍼티 변경
const changed = { ...{ x: 1, y: 2 }, y: 100 };
// changed = { ...{ x: 1, y: 2 }, ...{ y: 100 } }
console.log(changed); // { x: 1, y: 100 }
// 프로퍼티 추가
const added = { ...{ x: 1, y: 2 }, z: 0 };
// added = { ...{ x: 1, y: 2 }, ...{ z: 0 } }
console.log(added); // { x: 1, y: 2, z: 0 }
객체를 조작하는 유용한 방법이기도 하다
디스트럭처링 할당
디스트럭처링(구조 분해) 할당은 구조화된 배열과 같은 이터러블 또는 객체를 비구조화하여 1개 이상의 변수에 개별적으로 할당하는 것을 말한다
const arr = [1, 2, 3];
// ES6 배열 디스트럭처링 할당
// 변수 one, two, three를 선언하고 배열 arr을 디스트럭처링하여 할당한다.
// 이때 할당 기준은 배열의 인덱스다.
const [one, two, three] = arr;
console.log(one, two, three); // 1 2 3
기본적인 활용은 위와 같다
// Rest 요소
const [x, ...y] = [1, 2, 3];
console.log(x, y); // 1 [ 2, 3 ]
rest 요소를 활용한 비구조화 할당도 가능하다
const user = { firstName: 'Ungmo', lastName: 'Lee' };
// ES6 객체 디스트럭처링 할당
// 변수 lastName, firstName을 선언하고 user 객체를 디스트럭처링하여 할당한다.
// 이때 프로퍼티 키를 기준으로 디스트럭처링 할당이 이루어진다. 순서는 의미가 없다.
const { lastName, firstName } = user;
console.log(firstName, lastName); // Ungmo Lee
리터럴 형태로 객체 프로퍼티 키를 비구조화 할당이 가능하며 순서는 의미가 없다
const { firstName = 'Ungmo', lastName } = { lastName: 'Lee' };
console.log(firstName, lastName); // Ungmo Lee
const { firstName: fn = 'Ungmo', lastName: ln } = { lastName: 'Lee' };
console.log(fn, ln); // Ungmo Lee
객체 프로퍼티 값을 변수명으로 비구조화 할당을 받을 때는 주의가 필요하다
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'
// Rest 프로퍼티
const { x, ...rest } = { x: 1, y: 2, z: 3 };
console.log(x, rest); // 1 { y: 2, z: 3 }
위와 같이 다양한 활용이 가능하다
본 내용은 위키북스의 '모던 자바스크립트 Deep Dive'를 바탕으로 작성되었습니다.
'Languages > JavaScript' 카테고리의 다른 글
[HUFS/GnuVil] #21 Set과 Map, 비동기 프로그래밍 (0) | 2022.11.18 |
---|---|
[우아한테크코스/프리코스] #3 로또 (0) | 2022.11.16 |
[HUFS/GnuVil] #19 DOM (0) | 2022.11.11 |
[HUFS/GnuVil] #18 브라우저의 렌더링 과정 (0) | 2022.11.11 |
[우아한테크코스/프리코스] #2 숫자 야구 (0) | 2022.11.09 |