cheetsheet

length 프로퍼티 없이 이터레이터로 Each 구현하기

냠냠맨 2023. 3. 17. 13:43

🐕 과제를 하다가...

underbar라는 과제가 있는데 배열 내장 메서드 없이

배열 내장 메서드를 구현하는 내용이었습니다.

근데 전 length 프로퍼티도 사용하면 안되는 줄 알고

이걸 length 없이 어떻게 구현할까 고민하다가

결국 이터러블 프로토콜을 이용해서 직접 만들면되지않나? 라는 결론에 도달했어요


👻어케 함

다행히도 정적메서드는 사용해도 오류가 안 나길래 썼는데..

(생각해보면 정적메서드도 되는데 length를 막아놨다고 생각하는 거 자체가 이상하긴함)

그러면 맨 처음 생각나는 선택지는

while문을 만들어서 무한정으로 값을 올려주는

let idx = 0

while(arr[idx] !== undefined) {
  idx++
}

이런 선택지를 고려할 수 있을 것입니다.

하지만 이 방식에는 치명적인 문제점이 있어요

흔치는 않겠지만 배열의 요소에 의도적으로 undefined가 할당되어있는 경우

굉장히 이상하게 동작할텐데 그걸 예외처리해줄 방법도 딱히 없으니까요!!

결국 가장 확실한건 이터레이터와 넥스트메서드를 사용하는거라고 생각했습니다.

_.each = function (collection, iteratee) {
  if (Array.isArray(collection)) {
    let idx = 0;
    let areYouDone = collection[Symbol.iterator]();
    let doneBoolean = false;
    while (!doneBoolean) {
      const iResult = areYouDone.next();
      doneBoolean = iResult.done;
      if (doneBoolean) return;
      let iValue = iResult.value;
      iteratee(iValue, idx, collection);
      idx++;
    }
  } else {
    for (let key in collection) {
      iteratee(collection[key], key, collection);
    }
  }
  // TODO: 여기에 코드를 작성합니다.
};

따라서 이렇게 구현을 했는데 사실 처음부터 이런 구조였던게아닙니다.

첫번째 주의 사항

done 프로퍼티의 값을 받아오려면 next() 메서드를 호출해야하고

next()메서드는 어디서 호출되었든 호출되는 순간

다음 요소로 전진해버리기 때문에

한 반복에서 딱 한번만 next()메서드를 사용하고

그 값을 변수에 저장한 뒤 적절하게 사용해줄 필요가 있습니다.

 

따라서 iResult변수에 next() 메서드의 호출 결과(이터레이터 리절트 객체)를 담아준 뒤

그 값들을 활용해서 while문의 종료조건으로도 사용하고..

콜백의 인자로도 사용하고... 다 해주면 됩니다.

 

두번째 주의사항

done 프로퍼티의 밸류가 true로 바뀌는 시점은

마지막 요소를 만난 다음 next() 메서드입니다.

let arr = [1, 2, 3, 4, 5];

let a = arr[Symbol.iterator]();
console.log(a.next());
console.log(a.next());
console.log(a.next());
console.log(a.next());
console.log(a.next());
console.log(a.next());

따라서 위 코드의 출력결과를 생각해보면 쉽습니다.

{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
{ value: 5, done: false }
{ value: undefined, done: true }

배열의 길이는 5인데 next메서드는 총 6번을 호출해야

done이 true로 바뀝니다.

그럼 마지막 호출일땐 코드를 실행하지않고 끝내줘야 정상적으로 동작할거란 예상이 가능하죵

    let idx = 0;
    let areYouDone = collection[Symbol.iterator]();
    let doneBoolean = false;
    while (!doneBoolean) {
      const iResult = areYouDone.next();
      doneBoolean = iResult.done;
      if (doneBoolean) return;
      let iValue = iResult.value;
      iteratee(iValue, idx, collection);
      idx++;
    }

따라서 이렇게 코드를 작성하면

아주 멋진 나만의 forEach문이 완성되지만

원본 forEach문이 더 좋으니까 쓸모는 없겠네요 감사합니다.


이해했다고 생각할 때가 가장 무서울 때다.

 

반응형