iterable 객체
서문)
iterable(반복 가능한) 객체는 배열을 일반화한 객체이다
iterable 개념을 사용하면 어떤 객체든 for..of 반복문을 사용할 수 있다
iterable 개념 중에 대표적인 것이 배열인데 이외에도 다수의 내장 객체가 반복이 가능하다 (문자열…)
배열이 아닌 객체는, 어떤 것들의 목록이나 집합 등을 나타내고 있는 경우 for..of 문법을 적용해 순회할 수 있다
Symbol.iterator
// 객체에 for..of 적용하기
let range = {
from: 1,
to: 5
};
range[Symbol.iterator] = function() { // Symbol.iterator가 없으면 error
return {
current: this.from,
last: this.to,
next() {
if(this.current <= this.last) {
return{ done: false, value: this.current++ };
} else {
return { done : true };
}
}
};
};
for (let num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// range 자체에는 next() 메서드가 존재하지 않는다
// range[Symbol.iterator]를 호출해서 만든 iterator 객체와 이 객체의 메서드 next()에서 반복에 사용될 값을 만든다
iterable 객체의 핵심은 “관심사의 분리”
- 반복 대상인 객체와 iterator 객체 분리(위 예시에선 range 객체와 range[Symbol.iterator])
// iterator 객체 + 반복 대상인 객체 = range(iterator로 만들어버리기)
let range = {
from: 1,
to: 5
[Symbol.iterator] () {
this.current = this.from
return this;
},
next() {
if(this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
for (let num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
Q.iterable 객체를 사용하면서 두개의 for..of를 사용하는 경우는 과연 어떤 경우일까?
무한개의 iterator
위 예시에서 range.to에 infinity를 할당하면 range가 무한대가 된다
무수히 많은 의사 난수를 생성하는 iterable 객체를 만들게 될 수 있다
또한 next()엔 제약이 없기 때문에 무한대가 된 range의 값을 계속 반환하는 것도 정상적인 동작이다
그렇지만 break 를 사용하면 언제든지 반복을 멈출 수 있다
문자열은 iterable 이다
앞서 말했듯이 배열과 문자열은 iterable 개념 중 대표적인 내장 iterable 이다
// for..of는 문자열의 각 글자를 순회한다
for (let char of “test”) {
console.log(t, e, s, t)
}
//서로케이트 쌍에도 잘 동작한다
let str = '𝒳😂';
for (let char of str) {
console.log( char ); // 𝒳와 😂가 차례대로 출력됨
}
iterator를 명시적으로 호출하기(거의 없긴 하지만 더 디테일 하게 반복 과정을 통제할 수 있다고 한다)
// 직접 호출을 통해서 순회해보자(수동으로 값을 가져옴)
let str = “Hello”;
// for (let char of str) console.log(char) 과 똑같이 동작한다
let iterator = str[Symbol.iterator] ();
while (true) {
let result = iterator.next();
if (result.done) break;
console.log(result.value); // H, e, l, l, o
}
iterable과 유사 배열
- iterable: Symbol.iterator 메서드가 구현된 객체
- array-like(유사 배열): index와 length 프로퍼티가 있어서 배열처럼 보이는 객체
iterable ≠ array-like
주로 iterable 이면서 유사 배열 객체인 문자열을 볼 수 있다
// 유사 배열에서 Symbol.iterator이 없어서 에러가 발생한 상황
let arraLike = {
0: “Hi”,
1: “world”,
length: 2
};
for (let item od arrayLike) { } // TypeError: undefined is not a function (near '...item of arrayLike...')
둘 다 객체이므로 배열 메서드(pop, push etc.)를 사용할 수 없다
방법이 있을까?
Array.from(범용 메서드)
: iterable 객체와 array-like 객체를 받아 “Real Array” 를 (새로운 배열안에 객체 요소들을 넣어) 만들어 준다 ➡️ 배열 메서드 사용 가능 !
// array-like ➡️ 새로운 배열
let arraLike = {
0: “Hi”,
1: “world”,
length: 2
};
let arr = Array.from(arraylike);
console.log(arr.pop()); // world
Q.length: 2가 아닌 이유는 단순히 길이를 나타내는 프로퍼티여서?
// iterable ➡️ 새로운 배열
let range = {
from: 1,
to: 5
};
let arr = Array.from(range);
console.log(arr); // 1, 2, 3, 4, 5
//Array.from에 mapping function을 옵션으로 넣어줄 수 있다
Array.from(obj [, mapFn, thisArg])
let arr = Array.from(range, num => num * num);
console.log(arr);
Q.mapping function는 매핑가능한 적용할 함수를 가르키는 건가?
- 새로운 배열에 객체 요소를 넘겨주기 전 각 요소에 매핑함수를 적용해 반환할 수 있다
- thisArg는 각 요소의 this를 지정할 수 있게 해준다
// 문자열 ➡️ 새로운 배열
let str = '𝒳😂';
// str를 분해해 글자가 담긴 배열로 만듦, 서로게이트 쌍에도 적용된다
let chars = Array.from(str);
alert(chars[0]); // 𝒳
alert(chars[1]); // 😂
alert(chars.length); // 2
// Array.from을 사용하여 서로게이트 쌍을 처리할 수 있는 slice 구현하기
let slice = (str, start, end) {
return Array.from(str).slice(start, end).join(“”);
};
let str = “𝒳😂𩷶”;
console.log(slice(str, 1, 3); // 😂𩷶
// Array.from를 사용하지 않고 내장 str.slice를 사용하면 서로게이트 쌍을 처리하지 않는다