ES6에서의 순회와 이터러블:이터레이터 프로토콜

ddullgi·2022년 6월 7일
0
post-thumbnail

본 포스트는 인프런의 함수형 프로그래밍과 JavaScript ES6+ 강의(링크)를 듣고 정리한 내용입니다.


기존과 달라진 ES6에서의 리스트 순회

ES5에서의 리스트 순회(for i++)

const list = [1, 2, 3];
for (var i = 0; i < list.length; i++) {
    console.log(list[i]);
}
  • list.length라는 프로퍼티에 의존하고, 리스트 내부의 값을 숫자라는 key로 순회 하도록 i를 증가시키는 방식
  • 명령적이다.

ES6에서의 리스트 순회(for of)

const list = [1, 2, 3];
for (const a of list) {
    console.log(a);
}
  • 선언적이다.

Symbol.iterator

ES6에서 추가된 Symbol

Symbol.iterator는 어떤 객체의 키로 사용될 수 있다.


Array를 통해 알아보기

const arr = [1, 2, 3];
console.log(arr[Symbol.iterator]) //출력결과: ƒ values() { [native code] }
for (const a of arr) console.log(a);
//출력결과:
//1
//2
//3
  • arraykey값으로 배열의 내부 값에 접근 가능

    image-20220608012710125

  • 만약 Symbol.iterator의 값을 null로 설정할 경우 순회가 되지 않는다.

    arr[Symbol.iterator] = null;
    for (const a of arr) console.log(a);

    image-20220608013839379

    • 원래는 iterable 했다.

Set를 통해 알아보기

const set = new Set([1, 2, 3]);
console.log(set[Symbol.iterator]) //출력결과: ƒ values() { [native code] }
for (const a of set) console.log(a);
//출력결과:
//1
//2
//3
  • setkey값으로 내부 값에 접근 불가능

    image-20220608012806362

    • 즉, for i++의 방식으로는 배열 순회가 불가능 => for of의 내부 구조는 for i++와는 다르다.

Map를 통해 알아보기

const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
console.log(map[Symbol.iterator]) //출력결과: ƒ values() { [native code] }
for (const a of map) console.log(a);
//출력결과:
//(2) ['a', 1]
//(2) ['b', 2]
//(2) ['c', 3]
  • mapkey값으로 내부 값에 접근 불가능

    image-20220608012911176



이터러블/이터레이터 프로토콜

  • 이터러블: 이터레이터를 리턴하는 [Symbol.iterator]()를 가진 값

    • Array Set Map 모두 iterable이다.
  • 이터레이터: { value, done } 객체를 리턴하는 next()를 가진 값

    let iterator = arr[Symbol.iterator]();
    iterator.next() //{value: 1, done: false}
    iterator.next() //{value: 2, done: false}
    iterator.next() //{value: 3, done: false}
    iterator.next() //{value: undefined, done: true}
    • 순회가 끝나면 done: true가 된다.
  • 이터러블/이터레이터 프로토콜: 이터러블을 for...of, 전개 연산자 등과 함께 동작하도록한 규약


Array를 통해 알아보기2

const arr = [1, 2, 3];
let iter1 = arr[Symbol.iterator]();
iter1.next();
for (const a of iter1) console.log(a);
//2
//3

let iter2 = arr[Symbol.iterator]();
iter2.next();
iter2.next();
for (const a of iter2) console.log(a);
//3
  • 이터레이터에 next()를 실행 하면 for...of의 순회가 하나씩 뒤로 밀린다.
    • for...of는 이터러블한 객체의 이터레이터를 순회하며 실행된다.
    • Set Map 도 동일하다. (이터러블/이터레이터 프로토콜의 규약을 따른다.)

Map를 통해 알아보기2

const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
map.keys(); //MapIterator {'a', 'b', 'c'}
var a = map.keys();
a.next(); //{value: 'a', done: false}
a.next(); //{value: 'b', done: false}
a.next(); //{value: 'c', done: false}
a.next(); //{value: undefined, done: true}

for (const a of map.keys()) console.log(a);
//a
//b
//c

for (const a of map.values()) console.log(a);
//1
//2
//3

for (const a of map.entries()) console.log(a);
//(2) ['a', 1]
//(2) ['b', 2]
//(2) ['c', 3]
  • map.keys() 라는 함수는 이터레이터를 리턴한다.

  • 그리고 이 이터레이터를 실행하게 되면 valuekey만 남게 된다.

  • 리턴된 이터레이터도 이터러블이다!!

    • 즉, for...ofkey값만 순회가 가능하다.
  • value를 뽑는 values()와 내부의 keyvalueentry로 뽑아주는 entries()라는 함수도 존재한다.

사용자 정의 이터러블을 통해 알아보기

const iterable = {
  [Symbol.iterator]() {
    let i = 3;
    return {
      next() {
        return i === 0 ? { done: true } : { value: i--, done: false };
      },
    };
  },
};
let iterator = iterable[Symbol.iterator]();
// console.log(iterator.next()); //{ value: 3, done: false }
// console.log(iterator.next()); //{ value: 2, done: false }
// console.log(iterator.next()); //{ value: 1, done: false }
// console.log(iterator.next()); //{ done: true }

for (const a of iterable) {
  console.log(a);
}
//3
//2
//1

for (const a of iterator) {
  console.log(a);
}
//iterator is not iterable
  • iterable을 직접 구현해 봤다.
    • 이터레이터를 for...of에 넣었을 때 iterator is not iterable에러가 발생한다.

well-formed

  • 이터레이터가 자기자신을 반환하는 Symbol.iterator를 가지고 있을때 well-formed 이터레이터라고 한다.

  • Iterator이면서 Iterable인 객체를 말한다.

non well-formedwell-formed
이터러블이 진행된 지점을 기억하지 못해 처음부터 Restart이터러블이 진행된 지점을 기억하고 그 지점부터 Restart

const iterable = {
  [Symbol.iterator]() {
    let i = 3;
    return {
      next() {
        return i === 0 ? { done: true } : { value: i--, done: false };
      },
      [Symbol.iterator]() {
        return this;
      },
    };
  },
};
let iterator = iterable[Symbol.iterator]();

console.log(iterator[Symbol.iterator]() === iterator); //true

iterator.next();
for (const a of iterator) {
  console.log(a);
}
//2
//1
  • well-formed로 다시 구현해 보았다
    • 이터레이터가 자기자신을 반환하는 Symbol.iterator를 가지고 있다.
    • 이터러블이 진행된 지점을 기억하고 그 지점부터 시작한다.

Array Set Map 뿐만 아니라 DOM도 이터러블/이터레이터 프로토콜 규약을 따른다.

const all = document.querySelectorAll("*");
console.log(all[Symbol.iterator]); //ƒ values() { [native code] }
console.log(all[Symbol.iterator]()); //Array Iterator {}
let iter = all[Symbol.iterator](); 
console.log(iter.next()); //{value: html.focus-outline-visible, done: false}
console.log(iter.next()); //{value: head, done: false}
console.log(iter.next()); //{value: meta, done: false}



전개 연산자

const a = [1, 2];
const map = new Map([
  ["a", 1],
  ["b", 2],
]);
const b = [...a, ...map];
console.log(b);  //[ 1, 2, [ 'a', 1 ], [ 'b', 2 ] ]

for (const a of b) {
  console.log(a);
}
//1
//2
//[ 'a', 1 ]
//[ 'b', 2 ]
  • 전개 연산자로 다른 유형의 값을을 전개한 결과도 이터러블/이터레이터 프로토콜 규약을 따른다.
profile
프론트엔드개발자를 꿈꾸는 예비 개발자

0개의 댓글