JS_리스트 순회_Array, Set, Map
자바스크립트의 리스트 순회
ES6의 이전/ 이후 방식으로 크게 변경이 된 부분이라고 한다.
먼저, ES6 이전의 리스트 순회는 아래와 같이 for문을 사용하여 요소를 출력하였다.
const list = [1, 2, 3];
for (var i = 0; i < list.length; i++){
console.log(list(i));
}
반면, ES6 이후 for of, for in 등의 문법이 나오면서 아래와 같이 리스트 순회를 할 수 있게 되었다.
for of .. ==> 주로 배열반복에 사용되며
for in .. ==> 주로 객체 반복에 사용된다.
- for of 문법
const list = [1, 2, 3];
for (const a of list){
console.log(a);
}
- for in 문법
const AA = [1, 2, 3];
const BB = {'a':1, 'b':2, 'c': 3};
for (const a in AA){
console.log(a);
}
for (const a in BB){
console.log(a);
}
for of 문법 자세히 알아보기
1) Array
const arr = [1, 2, 3];
for (const a of arr) console.log(a);
2) Set
const set = new Set([1, 2, 3]);
for (const a of set) console.log(a);
3) Map
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for (const a of map) console.log(a);
ES6 이전의 리스트 순회를 다시 살펴보자
const list = [1, 2, 3];
for (var i = 0; i < list.length; i++){
console.log(list(i));
}
for of 문법은 내부적으로 위와 같은 순회 방식을 사용할까??
답은 그렇지 않다이다.
그렇다면 ES6에서 for of 문법이 어떻게 동작하고 어떻게 리스트를 순회하는지 알아보자.
아래의 예시들로 살펴볼 수 있다.
1) Arr
아래와 같이 배열의 Key로 접근하여 요소를 순회할 수 있다.
const arr = [1, 2, 3];
console.log(arr[0]); // 1
console.log(arr[2]); // 3
2. Set
Set 자료형의 경우는 Arr 와 달리 아래 명령어로 요소에 접근 할 수 없다.
이는 for or 문법이 내부적으로 ES6 이전 일반적인 순회방법과 다르게 구현되어 있음을 의미한다.
const set = new Set([1, 2, 3]);
console.log(set[0]); //undefined
console.log(set[2]); //undefined
3. Map
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
console.log(map[0]); // undefined
console.log(map[2]); // undefined
즉, 기존 리스트 순회와 같은 방법과는 다른 방법을 통해 for of 문법이 동작함을 알 수 있다.
for of 동작 원리
결론부터 말하자면 for of 문법은 iterable&iterator protocol 규약을 따른다.
동작 원리를 설명하기 위해선 ES6에서 새로이 추가된 Symbol을 먼저 알아야 한다.
Symbol
ES6에서 새로이 추가된 7번째 타입(number, string, boolean 등등)으로 변경 불가능한 원시 타입의 값이다.
심볼은 특정 객체의 고유한 식별자를 의미한다.
따라서 이름의 충돌 위험이 없는 유일한 객체의 프로퍼티 키를 만들기 위해 사용한다.
이는 특정 객체의 키로 사용될 수 있다.
Array, Set, Map은 자바스크립트에서 제공하는 내장 객체로서
iterable&iterator protocol 규약을 따르고 있다.
그렇다면 iterable / iterator / iterable&iterator protocol 에 대해 알아보자.
- Iterable : iterator를 리턴하는 [Symbol.iterator]() (심볼.이터레이터 메소드)를 가진 값.
- Iterator : { value : done } 객체를 리턴하는 next() (넥스트 메소드)를 가진 값.
- iterable&iterator protocol : Iterable을 for... of, 전개 연산자 등
여러가지 iterable&iterator protocol 를 따르는 다른 연산자 또는 문법들과 함께 동작하도록한 규약을 말한다.
위 개념들과 for... of 문법은 어떤 연관이 있을까?
Array === iterable 객체이다.
따라서 Array 는 Symbol.iterator Method 가지고 있으며 이를 통해 iterator를 리턴한다.
때문에 for...of 문과 함께 잘 동작하는 iterable Object로 for...of 순회를 할 수 있고
이는 곧, iterable&iterator protocol을 따른다고 말할 수 있다.
(위 텍스트에서 Array 는 Set, Map로 바꿔서 설명해도 문제가 없다.)
아래 디버그창에서 보이는 바와 같이 Symbol.iterator Method를 실행하면 iterator 를 리턴하는 것을 볼 수 있다.
이렇게 리턴한 iterator는 위 설명과 같이 { value , done} 이라는 객체를 리턴하고
이는 next Method를 가지고 있다.
next 메소드를 실행시킬때마다 아래와 같은 객체를 리턴하는 것을 볼 수 있으며
어느 순간부터 done => false -> true로 바뀌며 value는 undefined가 보인다는 것을 기억하고 넘어가자
1) Array
const arr = [1, 2, 3];
for (const a of arr) console.log(a);
위 이미지와 코드를 비교해가며 보도록하자.
next가 리턴하는 객체의 value 값을 const a 에 담아서 출력하고
다시 value 값을 담아서 출력하고를 반복하다가 done이 true가 되는 순간 for 문법에서 빠져나오는 것을 볼 수 있다.
아래 코드로 이해해보자.
next() 1번 실행 결과
const arr = [1, 2, 3];
let it2 = arr[Symbol.iterator]();
it2.next();
for (const a of it2){
console.log(a);
}
next() 2번 실행 결과
const arr = [1, 2, 3];
let it2 = arr[Symbol.iterator]();
it2.next();
it2.next();
for (const a of it2){
console.log(a);
}
next() 3번 실행 결과
const arr = [1, 2, 3];
let it2 = arr[Symbol.iterator]();
it2.next();
it2.next();
it2.next();
for (const a of it2){
console.log(a);
}
Array는 Symbol.iterator 를 실행한 it2라는 iterator를 계속해서 순회하면서 value 의 값을 출력하는 것이다.
(여기서 iterator를 순회한다는 것이 중요)
2) Set
Set 역시 Array와 마찬가지이다.
아래와 같이 키 값을 이용해 접근하지 못함에도 for...of 문법을 사용할 수 있는 이유는
증가하는 특정 i 값으로 접근해 순회하는 방법이 아닌 iterable&iterator protocol을 따르고 있고,
for...of 문 역시 iterable&iterator protocol 을 따르기 때문에 함께 동작할 수 있는 것이다.
const set = new Set([1, 2, 3]);
console.log(set[0]); //undefined
console.log(set[2]); //undefined
Set의 결과는 위 Array와 동일하다!
3) Map
Map 객체의 결과는 Array와 Set와는 조금 다르지만 원리는 같다.
value 안에 또 다른 Array가 담겨 있기 때문에
아래와 같은 출력 결과가 나오는 것이다.
Map 역시 next() 실행 후 평가를 하게 된다면 아래와 같이 순회하게 된다.
3-1) Map 의 Keys(), value(), entries() 함수
3-1-1) Keys()
Keys() 함수는 Iterator를 리턴한다. 이때 (객체의 키 값만을 리턴한다)
따라서 리턴된 iterator에서 next 함수를 실행한 경우 value에는 배열이 아닌 키값만 담겨지게 된다.
value에 배열이 아닌 키 값이 담기기 때문에 아래와 같이 순회하며 키값만을 출력할 수 있게 된다.
3-1-2) values()
Keys() 함수는 Iterator를 리턴한다. 이때 (객체의 값만을 리턴한다)
따라서 리턴된 iterator에서 next 함수를 실행한 경우 value에는 값만 담겨지게 된다.
3-1-3) entries()
기존 Symbol.iterator와 동일한 값을 리턴 및 출력해준다!
따라서 아래와 같이 순회 할 수 있다!
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for (const a of map.keys()) console.log(a);
for (const a of map.values()) console.log(a);
for (const a of map.entries()) console.log(a);
keys(), valus(), entries() 등 실행값으로 리턴되는 iterator 안에는 또 Symbol.iterator를 가지고 있다.
말이 조금 어렵지만, 프로토타입 객체가 자기만의 프로토타입 객체를 다시 갖는것과 유사하다고 생각한다.
처음 리턴된 iterator는 다시 Symbol.iterator 를 가지고 있기 때문에
for...of 문법에서 keys(), values() 등등 의 결과를 Symbol.iterator를 실행한 것을 가지고 다시 순회를 하는 것이다.
이때 Symbol.iterator는 또 다시 iterator 리턴하는데 이때 자기자신을 그대로 리턴하도록 되어 있다.
따라서 entries()로 만든 iterator를 Symbol.iterator()로 실행했을때 자기자신을 그대로 리턴하므로
it9 이 아닌 it10을 사용해도 원하는 결과가 나오게 되는 것이다.
SUMMARY
위와 같이 한마디로 정의하기 굉장히 어려운 과정을 iterable&iterator protocol이라 부른다!!!
Array, Set, Map iterable 객체임과 동시에, iterable&iterator protocol을 따르기 때문에
for...of 문법과 함께 동작할 수 있다.
for...of 문법의 동작원리는 한미디로 표현하자면
iterable&iterator protocol 이라고 할 수 있겠다!
Array, Set, Map은 Symbol.iterator Method 가지고 있으며 이를 통해 iterator를 리턴한다.
때문에 for...of 문과 함께 잘 동작하는 iterable Object로 for...of 순회를 할 수 있고
이는 곧, iterable&iterator protocol을 따른다고 말할 수 있다.