console.log([...stack], [...queue]);
for (const s of stack) console.log(s);
for (const q of queue) console.log(q);
const itStack = stack[Symbol.iterator]();
console.log(itStack.next());
console.log(itStack.next());
...
const itQueue = queue.iterator();
console.log(itQueue.next());
🚀 stack Stack {}
🚀 queue Queue {}
E:\AI\220702 fullstack\jsbasic\0813\velogClass2.js:75
console.log([...stack], [...queue]);
^
// terminal : TypeError: stack is not iterable
지난 번에 작성한 class 에서 스프레드 문법을 돌려봤다. 해당 작업은 typeError를 돌려줬고 stack은 이터러블 하지 않다고 한다. 그럼 여기서 iterable이 뭔지 알아봐야겠다.
iterable protocol 은 JavaScript 객체들이, 예를 들어 for..of 구조에서 어떠한 value 들이 loop 되는 것과 같은 iteration 동작을 정의하거나 사용자 정의하는 것을 허용합니다
반복하지 못하는 객체에 대해 반복가능한 프로토콜을 만들어주는 것이다.
어떤 built-in type 들은 Array 또는 Map 과 같은 default iteration 동작으로 built-in iterables 입니다.
String, Array, TypedArray, Map and Set
는 모두 내장 iterable이다. 이 객체들의 프로토타입 객체들은 모두 @@``iterator 메소드를 가지고 있기 때문이다.
Array나 Map 등 은 기본적으로 빌트인 이터러블을 지원하기 때문에 for...of문이나 스프레드 문법을 지원하지만, 다른 것들은 iterable 하게 만들어줘야한다.
딱딱한 객체를 조금 더 야들야들하게 만든다고 해야할까?
iterable 하기 위해서 object는 @@iterator 메소드를 구현해야 합니다. 이것은 object (또는 prototype chain 의 오브젝트 중 하나) 가 Symbol.iterator key 의 속성을 가져야 한다는 것을 의미합니다
Property : [Symbol.iterator]
Value : object를 반환하는, arguments 없는 function. iterator protocol 을 따른다.
굳이 Symbol type을 추가해준 이유는 사용자가 iterator 함수를 직접 구현하지말라고 강제하는 것이라고 한다. 중복방지
이터레이터(Iterator)는 현재 어디에 있고, 다음엔 어디로 가는지 알 수 있다. 이는 메모리 차원에서 이점이 있다.
value, done, next() 을 이용하여 더 쓸모있는 동작이 가능
한 객체를 만든다.
const cities = ['부산', '대구', '대전', '서울'];
const iter = cities.values();
//또는 const iter = cities[Symbol.iterator]();
iter.next(); // {value: '부산', done: false}
// ...
iter.next(); // {value: '서울', done: false}
iter.next(); // {value: undefined, done: true}
for(const x of iter) { console.log(x); }
// value 값(부산, 대구, 대전, 서울) for-of와 [...arr] 가능!
// ←→ index가 없어도, next()를 보유하고 있어 loop 가능!
for-of는 index가 없는데 어떻게 순서대로 loop를 돌지?
iterable 하다는 것은 index
가 없이도 순서대로 loop를 돌 수 있다는 말이다.
즉, for...of, spread(...instance) 가능하다
인스턴스 값을 iterable 하게 하기 위해서 [Symbol.iterator]
를 구현해보자
[Symbol.iterator]()
→ value, done, next()class Collection {
#arr;
constructor(...args) {
this.#arr = Array.isArray(args[0]) ? args[0] : [...args];
}
[Symbol.iterator]() {
return this.#arr.values();
}
iterator() {
// 관례적으로 이터레이터 함수를 만듬
return this[Symbol.iterator]();
}
...
}
const stack = new Stack(3, 3, 4, 2);
const queue = new Queue([1, 2]);
console.log(stack);
console.log([...stack, ...queue]); // [ 3, 3, 4, 2 ] [ 1, 2 ]
for (const s of stack) console.log(s);
for (const q of queue) console.log(q);
const itStack = stack[Symbol.iterator]();
console.log(itStack.next()); // { value: 3, done: false }
console.log(itStack.next()); // { value: 3, done: false }
console.log(itStack.next()); // { value: 4, done: false }
console.log(itStack.next()); // { value: 2, done: false }
console.log(itStack.next()); // { value: undefined, done: true }
super class
인 Collection에 [Symbol.iterator]()
를 구현하는 방법이다. return 값으로 this.#arr.values()
메서드를 사용해서 iterable 한 값을 리턴한다. iterator()
메서드도 구현하였는데 이는 관례적으로 만들어 놓는다고 한다.
하지만 출제자의 의도는 이것을 원하는 것이 아니었다. 제너레이터를 넘어가기 위한 과정을 만들어보고 싶었던 것이다.
// #2 next 함수 직접구현하기
[Symbol.iterator]() {
let idx = -1;
let done = false;
return {
next: () => {
idx += 1;
done ||= idx >= this.#arr.length;
return { value: this.#arr[idx], done };
},
};
}
클로저를 사용해 next()
메서드를 직접 구현한 것이다.
[Symbol.iterator]()
안에 idx 와 done 변수를 정의하고 이 함수 리턴 값에 객체로 next 함수를 정의한다. key 값으로 next를 갖고, next key 값의 value로는 익명 함수를 넣는다. idx는 함수가 실행될 때마다 1씩 증가한다. 근데 done에는 새로운 할당연산자가 있지 않은가?
The logical OR assignment (x ||= y) operator only assigns if x is falsy.
사실done = done || idx >= this._arr.length;
얘랑 같은건데 단축표현식이다.
아무튼 done은 done 이거나 idx가 this.#arr의 length보다 작을 때까지 false
를 반환한다. idx가 this.#arr.lengh 보다 같거나 클 때 true를 반환하므로 next가 끝이난다.
최종적으로 next()메서드와 같은 형태로 출력해주기 위해 {} 객체안에 value: this.#arr[idx] 값을 출력해주고 done 은 프로퍼티 축약 표현으로 key값만 적어준다.
next: () => {
idx += 1;
return { value: this.#arr[idx], done: !this.#arr[idx] };
},
더 단축해서 표현하자면 return에 해당 값을 넣을 수 있다.
value 에는 위와 같이 적어주고, done 에는 !를 앞에 붙여 값이 있을 땐 계속 falsy한 값이 나오다가 this.#arr의 인덱스를 벗어나는 순간부터 undefined가 나오기 때문에 undefined는 falsy 한 값이라 !와 붙으면 true 가 출력된다.
iterable 하지 않은 값을 iterable하게 만들어주는 것은 여러 의미가 있다. 여러 메서드를 쓸 수 있고, 메모리의 효율 차원에서도 도움이 된다. 사실 실무에서 iterator를 짤 일은 없다고 한다. 하지만 iterator를 알아야 다음에 나오는 generator 나 비동기를 이해하는 초석이 된다. 실무자 중에 iterator와 generator를 알지 못한다고 답한 비율이 80%라고 한다. 나는 20%에 들어갈 기회가 생겼다. 이 글을 읽은 당신도!
SSAC 영등포 교육기관에서 풀스택 실무 프로젝트 과정을 수강하고 있다. JS전반을 깊이 있게 배우고 실무에 사용되는 프로젝트를 다룬다. 앞으로 그 과정 중의 내용을 블로그에 다루고자 한다.