ES5에서의 리스트 순회
// ES5에서의 리스트 순회
const list = [1,2,3,4,5];
for(var i=0; i<list.length;i++){
console.log(i)
}
const str = 'abc';
for(var i=0; i<str.length;i++){
console.log(str[i])
}
ES5에서의 for문은 시작과 끝을 정의해주고 하나씩 증가하여 리스트를 순회 했었다. 이것은 length
라는 속성에 의존해야 하는 문제점이 있었다.
즉, 순회하려면 __proto__
또는 constructor
에서 length
를 프로토타입 기반으로 상속을 받아야 된다는 점이다.
ES6에서의 리스트 순회 (for of iterable)
- Array에서의 순회
const arr = [1,2,3];
for(const a of arr) console.log(a);
// 1
// 2
// 3
const str = 'abc'
for(const a of str) console.log(a);
// a
// b
// b
- Set에서의 순회
const set = new Set([1,2,3]);
for(const a of set) console.log(a);
// 1
// 2
// 3
- Map에서의 순회
const map = new Map([['a',1], ['b',2],['c',3]]);
for(const a of map) console.log(a);
// ['a', 1]
// ['b', 2]
// ['c', 3]
ES2015에 도입된 for of Iterable
이 어떤 식으로 작동하는지 자세히 알아볼 필요가 있다.
iterable 객체
반복 가능한(iterable, 이터러블) 객체는 배열을 일반화한 객체이다. for..of
구문과 함께 ES2015에서 도입되었다.
배열
은 대표적인 이터러블이다. 배열 외에도 다수의 내장 객체가 반복 가능한 이터러블이다. 문자열 역시 이터러블의 예이다.
배열이 아닌 객체(목록, 집합 등)도 for..of
문법을 적용할 수 있다면 컬렉션을 순회하는데 유용하다.
반복 가능한 객체와 아닌 객체를 구분짓는 큰 특징은, 객체의 Symbol.iterator
속성에 특별한 형태의 함수가 들어있다는 점이다.
객체의 Symbol.iterator
속성에 특정 형태의 함수가 들어있다면, 이를 반복 가능한 객체 즉 iterable
이라 부른다.
이를 iterable protocol
를 따른다고 말한다.
Symbol.iterator
밑의 코드를 보면 Array 타입의 arr
는 인덱스로 접근이 가능하지만 Set, Map은 인덱스로 접근이 불가능하다. 그런데 for..of
로 순회가 가능한 이유는 iterable protocol
를 따르기 때문이다.
const arr = [1,2,3];
const set = new Set([1,2,3]);
const map = new Map([['a',1], ['b',2], ['c',3]])
console.log(arr[0]) // 1
console.log(set[0]) // undefined
console.log(map[0]) // undefined
for(const a of arr) console.log(a); // 1 2 3
for(const a of set) console.log(a); // 1 2 3
for(const a of map) console.log(a); // ['a', 1] ['b', 2] ['c', 3]
아래의 코드를 보면 Array
객체에 Symbol.iterator
를 null로 주었을 때 arr
는 iterable
이 아니다라고 에러가 발생하는 것을 확인할 수 있다.
다시말해, iterable protocol
를 따른다는 것은 이터러블을 for..of
, 전개 연산자
등과 함께 동작한다는 규약이라고 이해하면 된다.
const arr = [1,2,3];
arr[Symbol.iterator] = null;
for(const a of arr) console.log(a);
// Uncaught TypeError: arr is not iterable
const arr = [1,2,3];
arr[Symbol.iterator] = null;
const arr2 = [...arr];
// Uncaught TypeError: arr is not iterable
Iterator Protocol
iterable
객체는 iterable Protocol
를 따른다고 했다. (이터러블과 이터레이터를 혼동할 수 있는데 잘 구분해서 이해해야한다.)
iterable Protocol
를 따른다는 말은 Symbol.iterator
속성에 특별한 형태의 함수가 저장되어 있다는 말이다.
iterable Protocol
를 만족하려면 Symbol.iterator
속성에 저장되어 있는 함수는 iterator
객체를 반환해야 한다.
iterator
는 아래의 조건을 만족하는 객체이다.
-
next
메서드를 갖는다. -
next
메서드는value
,done
두 속성을 갖는 객체를 반환한다.
위 조건을 iterator protocol
이라고 한다.
아래의 예제를 통해 쉽게 이해해보자.
// iterable Array 객체로부터 iterator를 생성한다.
const arr = [1,2,3];
const iterator = arr[Symbol.iterator]();
// iterator의 next 메서드를 통해 객체를 반환한다.
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}
// iterable Set 객체로부터 iterator를 생성한다.
const set = new Set([1,2,3]);
const iterator = set[Symbol.iterator]();
// iterator의 next 메서드를 통해 객체를 반환한다.
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}
// iterable Map 객체로부터 iterator를 생성한다.
const map = new Map([['a',1],['b',2],['c',3]]);
const iterator = map[Symbol.iterator]();
// iterator의 next 메서드를 통해 객체를 반환한다.
console.log(iterator.next()); // {value: Array(2), done: false}
console.log(iterator.next()); // {value: Array(2), done: false}
console.log(iterator.next()); // {value: Array(2), done: false}
console.log(iterator.next()); // {value: undefined, done: false}
Iterable protocol
과 iterator protocol
에 대해 이해가 됐다면, iterable
를 직접 구현해보자.
const iterable = {
[Symbol.iterator](){
let range = 1;
return {
next(){
return range<4 ? {value: range++, done: false} : {value: undefined, done: true}
}
}
}
}
const iterator = iterable[Symbol.iterator]();
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}
그럼 직접만든 이터러블 객체를 for..of
로 순회해보자. iterable
이 아니라고 에러가 발생할 것이다.
const iterable = {
[Symbol.iterator](){
let range = 1;
return {
next(){
return range<4 ? {value: range++, done: false} : {value: undefined, done: true}
}
}
}
}
const iterator = iterable[Symbol.iterator]();
for(const a of iterator) log(a); // Uncaught TypeError: iterator is not iterable
그 이유는 iterator도 Symbol.iterator
를 가지고 있으며, 이것은 자기 자신이다.
즉, iterator
는 자기 자신을(this)를 반환하는 Symbol.iterator
를 가지고 있다는 말이다.
const arr = [1,2,3];
const arr2 = arr[Symbol.iterator]();
console.log(arr2[Symbol.iterator]()==arr2) // true
이제 자기 자신을(this)를 반환하는 Symbol.iterator
메서드를 추가해주자.
const iterable = {
[Symbol.iterator](){
let range = 1;
return {
next(){
return range<4 ? {value: range++, done: false} : {value: undefined, done: true}
},
[Symbol.iterator](){
return this
}
}
}
}
const iterator = iterable[Symbol.iterator]();
for(const a of iterator) console.log(a); // 1 2 3
재밌는 사실
javascript
가 사용되는 환경인 Web API
라던지, DOM
과 같은 많은 값들도 이터러블&이터레이터 프로토콜
을 따른다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
querySelectorAll()
메서드는 NodeList
를 반환한다. 즉, 배열이 아닌데도 for..of
순회가 가능하다.
그 이유는 이터러블&이터레이터 프로토콜
따르고 Symbol.iterator
가 구현되어있기 때문이다.
const dom = document.querySelectorAll('*');
const iterator = dom[Symbol.iterator]();
for(const a of iterator) console.log(a);