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가 기본적으로 제공한다.
아하그러면 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)
단! 다시 말하지만 이터러블이 아닌 유사배열객체는 스프레드 못쓴다.