딥다이브 스터디 34,35 (이터러블, 스프레드)

김영현·2023년 11월 23일
1

이터레이션 프로토콜

ES6에서 도입된 순회 가능한 자료구조를 만들기 위해 도입됨.
ES6이전에는 각자 for..in, for...of, for등으로 순회를 하였으나 이젠 이터레이션 프로토콜(순회 규약)을 준수하게 만듬.
따라서 순회 가능한 자료구조는 모두 이터레이션 프토로콜을 준수함.

이터레이션 프로토콜이 뭘까?
이 기능은 두가지 프로토콜로 이루어져있음.

  • 이터러블 프로토콜
    Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접구현 or 상속받은 Symbol.iterator를 호출하면 이터레이터 프토로콜을 준수한 이터레이터를 반환함. 이러한 규약을 이터러블 프로토콜이라고함.
    아하 그러니까 위와 같은 규약이 이터러블 프로토콜이구나?
    참고로 이터러블 프로토콜을 준수한 객체를 이터러블이라고 하며 for..of문으로 순회 가능하고 스프레드문법, 디스트럭처링 할당의 대상이 됨.
    아따 길구만?
  • 이러테이터 프로토콜
    위에 서술했듯 Symbol.iteraotr를 호출하면 이터레이터를 반환함. 이터레이터란 next메서드를 소유하고 next를 호출시 이터러블을 순회하며 value, done을 프로퍼티로 갖는 이터레이터 리절트 객체(반복자 결과 객체)를 반환함! 이러한 규약을 이터레이터 프로토콜이라함.
    아하 위와 같은 규약은 이터레이터 프로토콜이군?
    이터레이터이터러블(반복 가능한 객체)를 탐색하기 위한 포인터 역할이다.
    => 단방향 링크드 리스트에서 다음 메모리의 주소를 갖고있는 것도 이터레이터라고 보면 되는구나.

이터러블

이터러블 프로토콜을 준수한 객체.
그러면 이터러블을 어떻게 판별할까?
for..of문, 스프레드, 디스트럭처링 할당으로 확인해볼 수 있으나 더 정확한 방법이 있음.

const isIterable = v => v !== null && typeof v[Symbol.iterator] === 'function';

아까 서술했듯 Symbol.iterator메서드를 지니고 있는지 체크하면 됨


객체({})는 이터러블이 아니구나?
디스트럭처링 할당과 스프레드 문법을 사용할 수 있는데 왜 아니지?
=> 2021년에 스프레드가 허용됨

오우...

이터레이터

Symbol.iterator를 호출하면 나오는 친구가 이녀석이다. 얘는 next를 메서드로 갖는다.


next는 요소를 순회하기위한 포인터 역할을 한다고 했다.
=> next메소드를 호출하면 한칸씩 전진!

빌트인 이터러블

순회가능한 객체를 JS가 기본적으로 제공한다.

  • Array
  • String
  • Map
  • Set
  • TypedArray
  • arguments
  • DOM 컬렉션

for...of문

아하그러면 for..of문은 사실 이터레이터를 호출해서 value를 담아서 넘겨주고 done이 true가되면 종료되는구나?
=> 실제로 그렇다.

이터러블과 유사배열객체

유사배열객체는 length프로퍼티를 갖고 인덱스로 프로퍼티접근이 가능한 객체. 길이가 있기에 for문으로 순회 가능.

const arrayLikeObj = {
	0:"A",
  	1:"B",
  	length:2,
}
//for문 사용가능 But, 이터러블은 아닌 일반 객체.

참고로 arguments, NodeList, HTMLCollection은 유사 배열 객체이면서 이터러블임. 배열도 원래 이터러블이 아니었으나 ES6와서 이터러블이 되었다.

이터러블이 아닌 유사배열객체에 스프레드, for..of, 디스트럭처링 할당사용하고 싶으면 Array.from으로 배열화 시키자!

왜 필요할까?

단순히 순회만 하는데도 이리 거창하게 규약까지 정하다니.
=> ES6이전 순회방식은 각양 각색. for..of, 디스트럭처링, 스프레드 등의 기능은 모두 순회가 필요함. 각양 각색의 순회방식을 다 만족시킬 수 없기에 하나의 인터페이스로 통일하였다.

사용자 정의 이터러블

이터레이션 프로토콜을 준수한 객체라면 이터러블이다.
만족하지 않더라도 사용자가 만족시켜주면 된다!

const fibo = {
	[Symbol.iterator](){
    	let [pre, cur] = [0, 1];
      	const max = 10; //피보나치 수열의 최대값
      
      	//Symbol.iterator 메서드는 next메서드를 소유한 itretor 반환해야함.
      	// next 메서드는 이터레이터 리절트 객체(결과 객체...)를 반환해야함!
      	return {
        	next() {
            	[pre, cur] = [cur, pre + cur];
              	return { value: cur, done: cur>=max };
            }
        }
    }
}

for(const num of fibonacci){
	console.log(num) //1 2 3 5 8
}

//스프레드
const arr = [...fibo];

//디스트럭처링
const [a, b, ...rest] = fibo;

최댓값이 10으로 정해져있어 좀 아쉽다. 최댓값을 인수로받아서 이터러블을 생헝하는 함수로 만들어볼 수도있다.

const fiboFunc = function (max){
    let [pre, cur] = [0, 1];
  	//클로저를 형성한다
      	return {
          	[Symbol.iterator](){
              return {
             	next() {
            	[pre, cur] = [cur, pre + cur];
              	return { value: cur, done: cur>=max };
            	}
             }
        }
    }

위의 함수는 [Symbol.iterator]를 반환한다.

const iterable = fiboFunc(5);
const iteraotr = iterable[Symbol.iterator];

iteraotr.next();
iteraotr.next();
...

생성할때마다 이렇게 해야한다면 굉장히 귀찮을 것이다.
이터러블 객체를 생성하는 함수가 아니라 이터러블 이면서 이터레이터인 객체를 생성하는 함수로 바꿔보자

const fiboFunc = function (max){
    let [pre, cur] = [0, 1];
  	//클로저를 형성한다
      	return {
          	[Symbol.iterator]() { return this; }
             	next() {
            	[pre, cur] = [cur, pre + cur];
              	return { value: cur, done: cur>=max };
       		}
  }

const iter = fiboFunc(10)
for(const num of iter){
	console.log(num) // 1 2 3 5 8
}

참고로 위의 이터러블지연평가(lazy evaluation)를 통해 데이터를 생성한다.
즉, 필요한 시점까지 데이터를 생성하지 않고 있다가 필요한 시점이 되면 그때야 데이터를 생성함.
결과가 필요할때 까지 평가를 늦추는 기법이다.
=> for..of문 등이 이터레이터의 next메서드 호출하기 전까지는 다음 값이 없음
불필요한 메모리를 소비하지 않으며, 길이의 한계가 없다.
다만 계산을 그때그때 해야한다는 단점이 있다.


스프레드 문법

하나의 뭉쳐 있는 여러 값들의 집합을 펼쳐, 전개, 분산해서 개별적인 값들의 목록으로 만듬.


아까 객체도 된다했는데 왜 안보일까?
참고로 { ...obj } 이렇게복제하는건 가능하다.

ES2018에서 확정된 제안
자세한건 여기나와있는데, 축약해서 설명하자면...


스프레드는 속성을 정의(Object.defineProperty)하고 Object.assign은 속성을 할당(setter)한다.

비슷한 기능이구나

참고로 스프레드로 펼쳐진 것은 값들의 목록이다.
따라서 스프레드 문법의 결과는 변수에 할당할 수는 없음.

const arr = [1, 2, 3]
const b = ...arr //SyntaxError:....

함수 호출문의 인수목록에서 사용할 때

const arr = [1,2,3]

//apply로 인수를 넘겨줌
Math.max.apply(null, arr)
//스프레드를 쓰면 간단
Math.max(...arr)

rest파라미터와 헷갈릴수 있음. 하지만 반대의 개념이니 유의하자.

function foo(...rest){
	console.log(rest) // [1, 2, 3] 이 나온다. 합쳐져서 나옴
}
foo(...[1,2,3]) // 1, 2, 3을 넣는다

배열 리터럴 내부에서 사용할 때

//concat
const arr = [1,2].concat([3,4,])
//spread
const arr = [...[1,2], ...[3,4]];
//splice
const arr1 = [1,4];
const arr2 = [2,3];
// [1,0].concat(arr2)는 [1,0,2,3]이되고 arr[1]까지 잘라 그 자리에 (2,3)을 삽입
Array.prototype.apply(arr1, [1,0].concat(arr2)); // [1,2,3,4]
//spread
arr1.splice(1, 0, ...arr2);

스프레드를 안쓸 이유가 없다.

이터러블을 배열로

ES5에서는 apply,call을 이용하여 slice메서드를 호출해야했다.

function sum(){
	var args = Array.prototype.slice.call(arguments);
  
  return args.reduce(function(pre, cur) {
  	return pre + cur;
  },0 )
}

이터러블이 아닌 유사객체로 이렇게 바꾸었다.
하지만 지금은 스프레드나 Array.from사용하면 멀끔해진다.

[...arguemnts]
Array.from(arguments)

단! 다시 말하지만 이터러블이 아닌 유사배열객체는 스프레드 못쓴다.

profile
모르는 것을 모른다고 하기

0개의 댓글