😎이터러블과 이터레이터
이터러블 프로토콜 | 이터레이터 프로토콜 |
Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환한다. 이것도 프로퍼티다 보니 일반 객체도 이터러블처럼 동작시키기 위해 Symbol.iterator 를 직접 프로퍼티키로 사용해서 이터러블을 만들어줄수도있다. |
이터러블의 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환한다고 했다 이터레이터는 다음과 같은 특징을 갖는다. |
for of문을 사용할 수 있다. | next() 메서드를 가지고 있다. next()메서드는 호출하면 이터러블을 순회하면서 value와 done 프로퍼티를 가지고있는 이터레이터 리절트 객체를 반환해준다. 이 규약을 이터레이터 프로토콜이라고 하고 이터레이터 프로토콜을 준수한 객체를 이터레이터라고 한다. |
스프레드 문법, 디스트럭처링 할당도 사용할 수 있다. | 이터레이터는 이터러블의 요소를 탐색하기 위한 포인터 역할 |
특징만 알고 갑시다
핵심은 이렇습니다.
1. Symbol.iterator 키를 가지고 있느냐 없느냐로 이터러블인지를 확인할 수 있다.
2. Symbol.iterator가 없는 일반 객체도 직접 구현을 통해 이터러블로 만들 수 있다.
3. 이터러블하면 for of문을 사용할 수 있다.
4. 이터러블의 Symbol.iterator메서드는 함수이고 그 함수는 이터레이터를 반환한다.
😎딥다이브의 그림 설명을 보면 다음과 같다.
그러니까 이터러블이라는 순회가능한 자료구조가 있고.
그 순회가능한 자료구조를 실제로 순회하기 위한 방법론이 이터레이터다.
라고 이해를 할 수 있겠네요
😎이터러블은...
// 이터러블을 직접 구현하는 방법
const iterable = {
[Symbol.iterator]() {
let cur = 1;
const max = 5;
return {
next() {
return { value: cur++, done: cur > max + 1 };
},
};
},
};
for (const num of iterable) {
console.log(num);
}
/*
1
2
3
4
5
*/
// 이터러블인지 엄밀히 확인하는 방법
const isIterable = (v) =>
v !== null && typeof v[Symbol.iterator] === 'function';
isIterable([]);
isIterable(new Map());
isIterable(new Set());
isIterable('');
이터러블 프로토콜을 준수한 객체를 이터러블이라고 합니다.
이터러블을 직접 구현하는 코드와
이터러블인지 엄밀하게 확인을 하는 코드는 다음과 같습니다.
이터러블을 확인하는 코드를 보면
[Symbol.iterator]가 함수면서 null이 아닌 경우를 들 수 있네요
어느정도는 [Symbol.iterator]만 해보면 알 수 있다
😎그럼 Symbol.iterator 메서드는..?
function hi(a, b, c, d, e) {
return arguments;
}
let a = hi(1, 2, 3, 4, 5, 6, 7, 8);
let iterator = a[Symbol.iterator];
console.log(iterator); // [Function: values]
console.log(typeof iterator); // function
이터러블인 객체는 Symbol.iterator를 가지고 있고 그 밸류로 함수를 갖고있다했습니다.
그렇다면 이 Symbol.iterator 프로퍼티가 가진 밸류는 바로 함수입니다.
우선 유사배열객체인 arguments를 반환하는 함수를 만들어놓고
a 변수에 할당해준 뒤 iterator 변수에 a의 [Symbol.iterator] 값을 할당해줬습니다.
그리고 console.log()를 통해 iterator에 담긴 값을 확인해보면 함수가 담겨있는것을 확인할 수 있습니다.
그럼 이번에는 저 함수를 호출해서 나온 결과값을 받아보도록하겠습니다.
음~ Array iterator라는 객체가 생겼습니다.
당연하지만 typeof의 결과는 다음과 같습니다.
console.log(typeof iterator) // object
그거야... 얘는 메서드를 가지고 있으니까...
아까 Symbol.iterator메서드가 반환하는 값은 이터레이터라고 했습니다.
그리고 이터레이터는 next()라는 메서드를 가지고있습니다.
그러니 최소한 얘는 메서드를 가질 수 있는 객체라는 것은 예상할 수 있다.
그럼 이 이터레이터가 가지고 있는 메서드들을 다시 집중해서 보겠습니다
다시 위의 그림으로 올라가보면
next()메서드와 Symbol.toStringTag 프로퍼티를 가지고 있는걸 확인할 수 있어요
next()메서드는 아까 보았듯이 이터레이터 리절트 객체를 반환해주는 메서드입니다.
그리고 value와 done 프로퍼티를 가지는데 이건 예시로 보면 아주 직관적입니다.
function hi(a, b, c, d, e) {
return arguments;
}
let a = hi(1, 2, 3, 4, 5, 6, 7, 8);
let iterator = a[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
/*
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
*/
아하~ value는 현재 순회중인 이터러블의 값을 뜻하겠군요
done은 끝났는지를 묻는거니 객체의 끝에 도달했느냐를 묻는것이겠구요
😎arguments 객체는 유사배열객체다. 그럼 유사배열 객체는...
그래서 arguments 객체를 찍어보면
length 프로퍼티도 가지고 있고..
Symbol.iterator도 가지고 있는것을 확인할 수 있습니다.
이렇듯 자바스크립트에서는 이터레이션 프로토콜을 준수한 객체인
빌트인 이터러블을 제공해줍니다.
다음 표를 참고하세요
빌트인 이터러블 Symbol.iterator 메서드 Array Array.prototype[Symbol.iterator] String String.prototype[Symbol.iterator] Map Map.prototype[Symbol.iterator] Set Set.prototype[Symbol.iterator] TypedArray TypedArray.prototype[Symbol.iterator] arguments arguments.prototype[Symbol.iterator] DOM 컬렉션 NodeList.prototype[Symbol.iterator]
HTMLCollection.prototype[Symbol.iterator]
유사배열 객체는
1. 인덱스로 프로퍼티값에 접근이 가능하고
2. length 프로퍼티를 가지는 객체를 말합니다.
따라서 for문 순회도 가능하고 배열처럼 프로퍼티값에 인덱스로 접근하는것도 가능하죠!
그리고... 그냥 일반적인 객체에도 키값을 인덱스처럼 0,1,2,3,4,5같은 식으로 만들고
length 프로퍼티를 만들어주기만하면 유사배열객체로 사용할 수 있습니다.
하지만...유사 배열 객체는 이터러블은 아니고 Symbol.iterator 메서드가 없기때문에
for-of 문으로 순회할 수는 없습니다.
이터러블로 만들어주려면 추가적으로 Symbol.iterator 프로퍼티도 만들어주고
안에 함수도 만들어주고 next() 메서드도 넣어줘야해요
엥 근데 우리 유사배열객체인 arguments로 지금까지 예제를 만들었잖아요?
그럼 지금까지 본건 뭐임? arguments도 Symbol.iterator있는데요?
그것은
arguments, NodeList,HTMLCollection은 유사배열객체이면서 이터러블이기 때문입니다.
ES6부터 이터러블이 도입되었는데 그 과정에서 유사배열 객체인 저 위 세녀석은
Symbol.iterator 메서드를 구현해서 이터러블이 되었고
그 이전에도 유사배열 객체여서 length프로퍼티 있고 인덱스로 접근가능하니
현재에는 이터러블이면서 유사배열객체인 혼종이 된 것입니다.
그렇기 때문에 우리도 문제없이 arguments로 예제를 뽑을 수 있고
아직 같이 예제를 하진 않았지만 arguments에 for-of문을 돌릴수도 있는거죠!
😎유사배열 객체,이터러블을 배열로 변환하는 법
a = Array.from(a);
console.log(a); [1,2,3,4,5,6,7,8]
console.log(Array.isArray(a)); // true
Array의 정적메서드 Array.from을 이용하면 쉽게 배열로 변환할 수 있습니다.
그러니까 우리가 지금까지 써온 예제인 arguments 유사배열객체를 배열로 한번 변환해봅시다.
그러니 이제 배열로 잘 변환이 되었는지 확인해주는
Array.isArray 정적메서드를 이용해 배열인지 확인을 해보자구요
Array.isArray의 반환결과도 true로 나오는 완전한 배열로 변환이 잘 되었네요!
😎그럼 이터레이션 프로토콜은 왜 필요할까?
데이터 공급자 | 이터레이션 프로토콜 | 데이터 소비자 |
Array | for..of | |
String | 스프레드문법 | |
Map/Set | 디스트럭처링 할당 | |
DOM 컬렉션 | Map/Set 생성자 |
결국 이터레이션 프로토콜은 순회할 수 있는 데이터를 제공하는 쪽과
순회할 수 있는 데이터를 기반으로 순회(반복)을 진행하는 데이터 소비자를
알맞게 연결시켜주기 위한 프로토콜(프로토콜은 어떤...일종의 규약? 약속?이라고 이해하시면 되겠습니다.)
이라고 볼 수 있습니다.
만약 데이터 공급자들이 각자 자기들만의 규칙을 갖고있다면
데이터 소비자들은 저 각각의 규칙들을 다 예외처리로 따로따로 구현해줘야할것입니다.
예컨대 String은 길이를 나타내는 프로퍼티의 키가 len이래!
근데 Array는 길이를 나타내는 프로퍼티의 키가 length래
이런식의 예외가... 난무한다면 정말 복잡하겠죠?
그래서 하나의 규칙을 정해서 데이터 공급자가 이런 식으로 데이터를 공급한다는
약속(프로토콜)을 정해두고 그것을 지켜주면
데이터 소비자는 그 규칙을 믿고 하나의 규칙을 따르는 로직을 짜면 끝이니
훨씬 효율적일것입니다.
따라서 이터레이션 프로토콜은 이런 이유로 필요성이 있을 수 밖에 없다.
알고보니 너무나도 당연한 논리네요!!
😎유사배열 객체에 forEach같은 배열메서드를 사용할 수도 있다.
function argMaker() {
return arguments;
}
let arg = argMaker(1, 2, 3, 4, 5, 6, 7, 8);
Array.prototype.forEach.call(arg, (ele) => {
console.log(ele);
});
/*
1
2
3
4
5
6
7
8
*/
뇌절하는것처럼 Array의...prototype의....forEach에.....call메서드를 써서....하면
이렇게 유사배열객체에 바로 배열메서드를 사용할 수 있습니다.
이게 뭐가 대단한건가요?라고 생각이 든다면
그냥 forEach를 돌려보는걸로 궁금증을 해소할 수 있을 것입니다.
function argMaker() {
return arguments;
}
let arg = argMaker(1, 2, 3, 4, 5, 6, 7, 8);
arg.forEach((ele) => console.log(ele)); //TypeError: arg.forEach is not a function
타입에러가 발생합니다. 왜 타입에러가 발생하는 것일까요?
그것 역시 console.log()를 통해 궁금증을 해소할 수 있을 것입니다.
function argMaker() {
return arguments;
}
let arg = argMaker(1, 2, 3, 4, 5, 6, 7, 8);
console.log(arg.forEach);//undefined
앗?? forEach 메서드가 없어서 undefined가 반환되는군요
그야...arguments는 그냥 객체이지 배열이 아니니까
Array.prototype과 프로토타입체인이 이어져있지 않기 때문에
배열메서드를 사용할 수 없는 것입니다.
https://xionwcfm.tistory.com/187
프로토타입에 대한 이해가 부족하시다면 위 글을 읽고오시면 됩니다.
아무튼 배열이 아니라 배열메서드를 못쓴다 정도로 이해하고 넘어가도 좋습니다
그렇다면 직접 Array의 prototype에 접근해서
Array.prototype이 가지고 있는 forEach 메서드를 가져와서 사용하는 수밖에 없겠군요
그런데 또 문제가 있습니다.
console.log(Array); // [Function: Array]
console.log(Array.prototype); // Object(0) []
console.log(Array.prototype.forEach); // [Function: forEach]
여러 역경을 넘어 타고타고 끝에 간 결과 만난 forEach는 함수입니다.
우리가 원하는건 arg 변수에 담겨있는 유사배열객체 arguments를 순회하는것입니다.
따라서 내가 순회하고싶은건 arguments야!라는 것을 컴퓨터에게 알려줘야합니다.
이것을 위해 call 메서드를 사용합니다
call 메서드는 첫번째 인자로 받은 값을 객체를 호출한 함수의 this에 바인딩해줍니다.
(지금은 그냥 사용법만 알면 되겠습니다.)
function argMaker() {
return arguments;
}
let arg = argMaker(1, 2, 3, 4, 5, 6, 7, 8);
Array.prototype.forEach.call(arg, (ele) => {
console.log(ele);
});
그래서 이렇게 작성하면 유사배열객체에 배열메서드를 사용하는게 가능해지는거죠!
😎이 이터러블은 내가 만들수도 있습니다.
// 이터러블을 직접 구현하는 방법
const iterable = {
[Symbol.iterator]() {
let cur = 1;
const max = 5;
return {
next() {
return { value: cur++, done: cur > max + 1 };
},
};
},
};
아까 위에서 이터러블을 내가 직접 만들수도 있다고한것 기억하시나요?
이터러블이 아닌 일반 객체도 조금의 수고를 들이면 이터러블로 만들어줄 수 있습니다.
바로 위의 예제코드와 같이 말이죠!
어..뭐 만들수있구나 그래서 뭐? 할 수도 있을거에요...맞아요..
하지만!!!!!!!!!!!!!!!!!!!!!!!!!!!
이것을 활용해서 무한 이터러블을 생성할수도있고 또 그것을 이용해서
지연평가까지도 경험을 해볼 수 있지만.... 그것까지 다루게되면
그것 하나만으로도 글 하나가 뚝딱 나오게 될만한 내용이기도하니
지금은 이런게 있구나... 정도로만 넘어가도록하겠습니다.
but!!!!!!!!!!!!!!!!!!!!!!!!!!!!
기억해야할 것은 유사배열객체, 이터러블과 같은 것을
내가 직접 코드를 짜서 만들 수도 있다는 것입니다.
이 전제를 알고 있어야 거기서 한걸음 더 나아간 무한 이터러블도
내가 직접 코드를 짜서 만드는 것이다.라는 인식까지 도달할 수 있을테니까요
다들 수고하셨읍니다.
'javascript' 카테고리의 다른 글
이벤트 루프와 블록 컨텍스트를 알아보자. (0) | 2023.03.10 |
---|---|
생존법칙1 reduce 메서드를 이해해라. (0) | 2023.03.09 |
너무 재미있는 예제로 알아보는 실행컨텍스트 (0) | 2023.03.05 |
getter,setter까지 복사하는 deep copy를 구현하자 (0) | 2023.03.02 |
프로토타입을 케이크처럼 쉽게 먹는 방법 (3) | 2023.02.28 |