시작되었읍니다.. 제너레이터로 가기 이전의 첫번째 지옥열차를 탑승하신 것을 축하합니다..
이터레이터를 들어가기 전에, 먼저 우리가 모던자바스크립트 책에서 자주 볼 수 있었던 '유사 배열 객체' 에 대해서 공부해볼까요?
#유사배열객체
● 유사 배열 객체 const arr = [10, 11, 12, 13] 우리가 흔히 잘알고 있는 배열은 '유사 배열 객체' 입니다. 이 배열을 콘솔로 찍어보면 콘솔창에서 0, 1, 2, 3 을 프로퍼티로 가지고 있씁니다. 아니 이런
ddaeunbb.tistory.com
객체를 이렇게 까지 배열로 만들어야하는 이유는 뭘까요? 배열의 메서들을 활용하고 싶어서일까요?
우린 왜 이렇게 까지 배열의 메서드를 사용하고 싶을까요?
자바스크립트의 새로운 문법들은 대부분 어떠한 '이유'가 있어서 만들어지는게 분명합니다. 그리고 그 이유에 대해 잘 파악하고 있게 된다면 개념도 조금 더 이해하기 쉬워집니다.
곰곰히 앉아서 생각해보니 배열은 다양한 메서드를 가지고 있습니다. 우리는 배열을 자를 수도 있고 배열을 붙일 수도 있고 배열들의 요소를 돌며 더할 수도 있고, 다른 형태로 변환시킬 수도 있습니다. 하지만 객체는 어떨까요?
객체는 배열처럼 편하게 데이터를 다룰 수 있는 메서드가 없습니다. 객체를 돌기 위해서는 for ... in 밖에 쓸 수 없으며 이 for in문 또한 순서있게 객체를 도는 것이 아닙니다. 데이터를 자유자재로 다뤄 HTML에 보여줘야하는 개발자입장으로서 이는 매우 불편한 상황이라고 할 수 있죠.. WTF.. 심지어 Object.keys, Object.values, Object.entries라는 메서드를 활용해서 배열로 만들어 데이터를 다뤄야합니다. 잡화스크립트 만든 넘들아. . 객체 메서드만들라고..
그러면 순서 있게 이 객체들을 주루룩 보여 줄 수 없을까요?
않이 잠깐만!
생각해보니, 배열도 객ㅊㅔ 아니였읍니까?
객체도 요소마다 키 값이 있었기 때문에 bracket notation으로 접근할 수 있었던 것 않입니까? 그럼 객체는 왜 for in문만 돌 수 있고 of문은 돌 수 업는 것 입니까? 이것을 해명해 주십시오.
●이터러블
이터러블(interable)이란 자료를 반복할 수 있는 객체를 말합니다.
그럼 당연히 배열은 이터러블이겠군요. 배열 객체라 하지 않았읍니까? 않이 근데 왜 그냥 객체는 안돌려지고 배열만 돌려지냐구요.
그 원리는 [Symbol.iterator] 땜문입니다.
const arr = [1,2,3,4,5]
for(const element of arr){
console.log(element)
}
위의 예제는 당연히 우리가 아는 바와 같이, 배열의 요소들이 하나씩 출력됩니다. 그럼 아래는 어케댈까염?
const arr = [1,2,3,4,5]
// arr의 [Symbol.iterator]라는 속성을 null로 처리해주었습니다.
arr[Symbol.iterator] = null;
for(const element of arr){
console.log(element);
}
그랬더니 arr이 이터러블하지 않다며 오류를 박아버리네요. 그렇습니다. 배열이 하나씩 돌아가는 이유는 이터러블했기 때문입니다.
즉, 이터러블은 Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나, 프로토타입 체인을 통해 상속받은 객체를 말합니다.
그니까 즉, 객체가 [Symbol.iterator] 이라는 메서드를 갖고 있으면 이터러블 이라고 보는 것입니다. (직접 메서드를 추가해줬든 프로토타입으로 상속을 받았든)
이터러블 객체인지 함수는 아래와 같습니다.
const isIterable = (check)=>{
console.log(check !== null && typeof check[Symbol.iterator] === 'function')
}
// 배열은 이터러블이다.
isIterable([]) // true
// 객체리터럴로 만든 객체는 이터러블이 아니다.
isIterable({}) // false
// 문자열은 이터러블이다.
isIterable('') // true
// Map, Set 모두 이터러블이다.
isIterable(new Map()) // true
isIterable(new Set()) // true
이터러블 객체는 for ... of 문으로 순회할 수 있으며, 스프레드 문법과 디스트럭처링 할당의 대상으로 사용할 수 있습니다.
const array = [1,2,3,4,5]
console.log(Symbol.iterator in array) // true
for(const element of array){
console.log(element) // 반복문 돌아감
}
console.log([...array]) // [1,2,3,4,5]
const [a, ...rest] = array;
console.log(a) // 1
그니까 쉽게 말하면, 객체에 Symbol.iterator 를 메서드를 가지고 있으면 이터러블이라고 하는 것임.
않이 그럼 이 Symbol.iterator는 역할이 뭔디요?
●이터레이터
Symbol.iterator는 이터레이터를 만들어줍니다.
이터레이터는 next 메서드를 갖고 있는데요. 이터레이터가 next 메서드를 호출하면 순차적으로 이터러블을 돌면서 리절트 객체를 반환합니다.
const arr = [1,2,3,4,5]
const iterator = arr[Symbol.iterator](); // arr의 이터레이터 만들어줌
console.log('next' in iterator) // true
console.log(iterator.next()) // {value: 1, done: false}
console.log(iterator.next()) // {value: 2, done: false}
console.log(iterator.next()) // {value: 3, done: false}
- 객체가 Symbol.iterator라는 메서드를 갖고 있어야 이터러블하다고 했습니다.
- 그리고 Symbol.iterator라는 메서드 호출하면 이터레이터를 반환합니다.
- 그리고 이 이터레이터의 next 메서드로 이터러블을 돌 수 있는 것입니다.
즉, 객체마다 값이 다를 것 아닙니까? 그럼 Symbol.iterator를 호출해서 각 객체에 맞게 이터레이터(요소를 도는 기계)를 생성하는 것입니다. 그 기계를 next로 돌리면 돌려진다 이거죠.
아니 그럼 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[Symbol.iterator] |
DOM 컬렉션 | NodeList.prototype[Symbol.iterator] HTMLCollection.prototype[Symbol.iterator] |
● for ••• of 문
그렇다면 for of 문은 어떻게 작동할 수 있는걸까요?
const arr = [1,2,3,4,5]
for(const element of arr){
console.log(element);
}
위의 for 문은
const arr = [1,2,3,4,5]
const iterator = arr[Symbol.iterator]();
for(;;){
const res = iterator.next();
if(res.done) break;
else return res.value;
}
이런식으로 작동하는 것입니다.
● 이터러블과 유사 배열 객체
모든 유사 배열 객체가 이터러블일까요? NO!
배열, arguments, NodeList, HTMLCollection 같은 경우 유사배열 객체이면서 이터러블입니다. 유사배열 객체들은 Array.from으로 배열을 만들 수 있었죠? (length)라는 프로퍼티를 가지고 있었기 때문입니다.
하지만, 모든 유사 배열 객체는 이터러블이 아닙니다.
아래는 유사 배열 객체입니다. lenght 라는 프로퍼티도 갖고 있죠?
const obj = {
0 : 'a',
1 : 'b',
2 : 'c',
length : 3
}
console.log(Array.from(obj)) // [ 'a', 'b', 'c' ]
하지만 Symbol.iterator 라는 프로퍼티는 갖고 있지 않습니다. 따라서 이터러블이 아닙니다. 즉, 일반 객체는 이터러블이라고 할 수 없겠죠.
이터러블이 될라면 우째야한다? Symbol.iterator라는 메서드를 갖고 있어야된다.!!!!!!!!!
● 사용자 정의 이터러블
그럼 내가 그냥 일반 객체에 Symbol.iterator 넣을 수 없삼? 내가 직접 커스텀하면 안되냐 이겁니다.
const obj = {
0 : 'a',
1 : 'b',
2 : 'c',
length : 3
}
우리는 위의 객체에 Symbol.iterator 객체를 넣기로 했으빈다.
그럼 아래와 같아집니다.
const obj = {
0 : 'a',
1 : 'b',
2 : 'c',
length : 3,
[Symbol.iterator](){
let count = -1;
let cur = this[count]
const end = this[this.length - 1];
return {
next(){
count++;
cur = obj[count]
return { value : cur, done : count === 3}
}
}
}
}
const iterator = obj[Symbol.iterator]();
console.log(iterator.next()) // {value: 'a', done: false}
console.log(iterator.next()) // {value: 'b', done: false}
console.log(iterator.next()) // {value: 'c', done: false}
console.log(iterator.next()) // {value: undefined, done: true}
힘드네요.. 굳이 이렇게 Symbol.iterator 함수를 만들어야하나요?••• ㅠ ㅠ 주르륵..
for (const element of obj){
console.log(element)
}
const objToArr = [...obj]
console.log(objToArr) // [ 'a', 'b', 'c' ]
객체가 이터러블이니 of문으로 돌 수도 있고, 스프레드 문법도 활용할 수 있습니다.
⚬ 이터러블을 생성하는 함수
이터러블을 리턴하는 함수를 만들어서, 이터러블을 만들 수 있다.
const fibonacciFunc = function(max){
let [pre, cur] = [0, 1];
return{
[Symbol.iterator](){
return{
next(){
[pre, cur] = [cur, pre+cur];
return { value : cur, done : cur >= max};
}
}
}
}
}
for(const ele of fibonacciFunc(10)){
console.log(ele) // 1 2 3 5 8
}
⚬ 이터러블이면서, 이터레이터인 객체를 생성하는 함수
const fibonacciFunc = function(max){
let [pre, cur] = [0, 1];
return{
[Symbol.iterator](){ return this;},
next(){
[pre, cur] = [cur, pre+cur];
return { value : cur, done : cur >= max};
}
}
}
여기서 return this는 함수가 return 하는 객체 그자체를 말한다 즉, 이터러블그잡채
for( const element of fibonacciFunc(10)){
console.log(element)
}
const iter = fibonacciFunc(10);
console.log(iter.next()); // {value: 1, done: false}
console.log(iter.next()); // {value: 2, done: false}
console.log(iter.next()); // {value: 3, done: false}
⚬ 무한 이터러블과 지연평가
const fibonacciFunc = function(){
let [pre, cur] = [0, 1];
return{
[Symbol.iterator](){ return this;},
next(){
[pre, cur] = [cur, pre+cur];
return { value : cur};
}
}
}
for(const element of fibonacciFunc()){
if( element > 100) break;
console.log(element)
}
불리언 평가하는 거 없애버리면 무한 수열되어버림;;
따라서 무한 수열을 만들 수 있게 되고, 지연 평가(Lazy Evaluation)가 가능해진다.
지연평가는 데이터가 필요한 시점 이전까지는 미리 데이터를 새엇ㅇ하지 않다가 데이터가 필요한 시점이 되면 그때야 비로소 데이터를 생성하는 기법입니다. 즉, 평가 결과가 필요할 때까지 평가를 늦추는 기법이 지연 평가입니다.
일반 객체는 왜 이터러블이 아닌지 불평불만을 가졌다가 이터레이터를 직접만들고, 이터러블인 동시에 이터레이터를인 객체를 내뱉는 함수까지 만들어보며 조져졌습니다.
일반 객ㄱ체 그냥 쓰겠습니다. 죗옹합니다.