4 분 소요

오늘 한 일

  • 이터러블 문법

Javascript에서 Iterable 문법에 대해 공부했다. Iterable은 반복하다, 순회하다 라는 뜻 자바스크립트에선 이터레이션 프로토콜 (규격,약속,인터페이스)을 따른다. 즉 순회가 가능하다.

자바스크립트에서 이터레이션 프로토콜을 따르는 객체는 for…of나 spread 연산자를 사용할 수 있다. 이것은 순회가 가능한 연산자다.

이터레이션 프로토콜을 따르는 기본 자바스크립트 자료구조(데이터타입)는 배열, 문자열, Map, Set 이런 것들이 있다. 그렇기 때문에 for…of나 spread연산자 사용이 가능하다.

이 연산자를 사용해서 무언가 순회가 가능하다. 이터레이션 프로토콜 (규격,약속,인터페이스)을 따른다는 것은

어떤 객체든지 순회가 가능하기 위해서는 이터러블 프로토콜을 따라야한다. 순회가 가능한 객체가 되려면 이터러블 프로토콜을 따라야한다.

{
	[Symbol.iterator](); Iterator 프로토콜
	      {
			   next(); 다음값
	}
}

어떤 객체 안에서도 심볼.이터레이터 함수를 호출했을 때 이터러블 프로토콜을 따르는 객체를 반환만 하면 이 객체는 순회가 가능한 객체다. 이터레이션 프로토콜을 따르는 객체다 라고 부를 수 있다.

내가 순회가 가능한 오브젝트가 되려면 내 오브젝트 안에 Symbol.iterator; 라는 이름의 함수를 만들면 되고 그 함수에서 이터러블 프로토콜을 따르는

즉, 순회하는 반복자를 리턴하는 객체를 만들면 된다.

Iterable : 순회가 가능한

Iterator : 순회하는, 반복하는 사람, 반복자

반환되는 이터레이터 프로토클을 따른다는 것은 무슨말이냐면

객체를 리턴하면 된다. 그 객체 안에서 next 라는 함수를 정의하면 된다. 그리고 next 함수를 호출할 때마다 바로 다음 값을 리턴하면 된다

배열로 순회할 수 있지만 이터레이터로 하게되면 모든 배열 아이템을 메모리에 올려놓는 배열관 다르게 값을 미리 다 계산해서 보관할 필요 없이 다음값 호출을 받았을 때 그 때 필요한 값만 계산하는 것이 가능하기 때문에 퍼포먼스나 용량차이가 많이 날 수 밖에 없다.

그리고 배열은 순회 기능 뿐만 아니라 다양한 기능도 많이 갖고 있어서 이터레이터보다 무겁다는 이유도 있다.

함수형 프로그래밍을 위한 map과 reduce와 같은 메서드들은 이터레이터만으로도 구현이 가능하다. 하지만 자바스크립트에선 배열에 붙어있다.

이터레이터와 배열을 구분할 필요도 없지만 구분을 할 수 없는 초보자들을 위한 접근이라고 생각하면 좋다.

배열도 이터레이션 프로토콜을 따르기 때문에 for…of를 통해 순회가 가능하다.

const array = [1, 2, 3];
for (const item of array) {
  console.log(item); // 1, 2, 3 하나하나씩 순회하며 반환
}

array 안에 다양한 함수 중 values도 이터레이션 프로토콜을 따르기 때문에 for…of 를 통한 순회가 가능하다.

이처럼 Symbol.iterator;함수를 포함한 이터레이션 프로토콜을 따르는 함수들도 for…of 등을 통해 순회가 가능하다. values, keys, entries

const array = [1, 2, 3];
for (const item of array.values()) {
  console.log(item); // 1, 2, 3 하나하나씩 순회하며 반환
}
const array = [1, 2, 3];
for (const item of array.keys()) {
  console.log(item); // 0, 1, 2 배열안의 키들을 다 호출
}
const array = [1, 2, 3];
for (const item of array.entries()) {
  console.log(item); //
}
// [0, 1],[1, 2],[2, 3]
// 0번째 인덱스는 1, 1번째 인덱스는 2, 2번째 인덱스는 3

오브젝트는 이터레이션 프로토콜을 따르지 않기 때문에 for…of 를 사용할 수 없다.(Symbol.iterator;라는 함수 정의가 없기때문.) 오브젝트 안에 [Symbol.iterator]라는 함수를 next가 호출될 수 있고 value와 done이 출력되도록 직접 만들어주면 가능하다.

const obj = {
  0: 1,
  1: 2,
};
for (const item of obj) {
  console.log(item);
} // 에러 (obj is not iterable)

정리하자면,

Iterable 하다는 것은 순회가 가능하다는 걸 의미하고

순회가 가능하기 위해서는 이터레이션 프로토콜이란 규격을 따르면 된다.

이 규격은 객체 안에 Symbol.iterator;라는 함수를 호출했을 때

next, next 등의 호출이 가능한 iterator;를 반환하면 된다.

이렇게 심볼 정의를 가진 객체나, 또는 특정한 함수가 이터레이터를 리턴한다는 것은 순회가능한 객체이다라는 것을 알 수 있다.

순회가 가능하면 무엇을 할 수 있냐면 빙긍빙글 도는 연산자인

for…of나 spread 연산자를 사용할 수 있다.

이런 규격상을 따르지 않는 일반 객체라면 이런 연산자를 사용하게 되면 에러가 발생한다.

대신에 for…in 이라는 연산자를 사용할 수 있다. (순회하며 key가 출력됨.)

const obj = {
  0: 1,
  1: 2,
};
for (const item in obj) {
  console.log(item);
} // 0, 1 key가 출력된다.

수동적으로 next()를 이용해서 순회하는 방법

const array = [1, 2, 3];
const iterator = array.values();
console.log(iterator.next());
// { value: 1, done: false }
// 현재 값과 마지막 아이템이 아니고 반복이 끝나지 않아서 false !

next를 호출할때마다 값을 감싸고 있는 객체가 리턴이 된다.

value라는 키에는 실제 값이, done라는 키에는 반복이 끝났는지 안끝났는지, 제일 마지막 아이템인지 나타내주는 것을 확인 할 수 있다.

이처럼 이터레이터를 받아서 for…of를 받아서 순회해도 되고

값을 하나만 확인하고 싶다면 next를 수동적으로 호출해서 값을 받을 수 있다.

const array = [1, 2, 3];
const iterator = array.values();
console.log(iterator.next().value); // done은 출력되지 않고 값만 출력된다. 1
console.log(iterator.next().value); // 2

배열의 아이템을 다 순회한 후에 한번 더 next를 호출하게 되면

value는 undefined가 출력되고 done은 true가 출력된다.(반복이 끝남을 의미)

const array = [1, 2, 3];
const iterator = array.values();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
console.log(iterator.next().value); // undefined
// value가 아닌 done은 true가 출력됨.

위의 반복되는 next의 코드들을 for…of 연산자를 사용하지 않고 아래처럼 깔끔하게 작성하는게 가능하다.

const array = [1, 2, 3];
const iterator = array.values();
while(true) {
	const item = iterator.next();
	if(item.done) break; // done이 순회가 끝나면 true가 되는 것을 이용
	console.log(item.value;) // 1,2,3
}

<0부터 10이하까지 숫자의 2배를 순회하는 이터레이터 만드는 방법>

  1. 그냥 순회하면 0,1,2,3….,9
  2. 2배를 순회하면 0,2,4,6, 8,10.. 18
  3. Symbol.iterator: Iterator { next(): {value:, done: }};
const multiple = {
	[Symbol.iterator]() {
		const max = 10;
		let num = 0;
		return {
			next() {
				return { value: num++ * 2, done: num > max }
			},
		};
	},
};
for(const num of multiple) {
	console.log(num); // 0,2,4,6,8...,18
}

재사용 가능하도록 함수버전으로 만들어보면

function makeIterable(initialValue, maxValue, callback) {
  return {
    [Symbol.iterator]() {
      let num = initialValue;
      return {
        next() {
          return { value: callback(num++), done: initialValue > maxValue };
        },
      };
    },
  };
  const multiple = makeIterable(0, 10, (n) => n * 2);
  for (const num of multiple) {
    console.log(num);
  }
}

콜백함수를 통해 n*2 를 n으로 반환만 하게 수정하면 순회하면서 기존이 value를 출력하게도 가능하고 재사용이 가능해진다. 즉 이터레이션에서 변경되는 것만 인자로 뽑아서 함수로 만들면 재사용이 가능해진다.

오늘 느낀 점

이터러블 문법 개념은 이해가 가고 왜 사용하는지도 알겠는데 아직 너무 낯설고 어렵게 느껴진다. 이터러블을 응용하고 다루기엔 아직 시작단계라서 이렇게 어렵게 다가오는 것 같다. 이터러블을 내일 더 공부해보고 순회를 좀더 쉽게 할 수 있는 제너레이터도 같이 공부해볼 생각이다.

태그:

카테고리:

업데이트:

댓글남기기