이터레이션 프로토콜
ES6에서 도입된 이터레이션 프로토콜은 데이터 컬렉션을 순회하기 위한 프로토콜입니다. ES6에서 도입되었으며 크게 두 가지로 구분됩니다.
1. 이터러블 프로토콜
Symbol.iterator 메서드를 구현하여 이터레이터를 반환하고, 자바스크립트의 대표적인 이터러블한 데이터 컬렉션은 Array, String, Map, Set 등이 있습니다. for ... of 문으로 순회가 가능하며, ...(스프레드 연산자)를 사용할 수 있습니다.
2. 이터레이터 프로토콜
next() 메소드를 갖고 있습니다. next() 메소드는 value와 done 프로퍼티를 가진 객체를 반환합니다. value는 현재 순회 중인 값을 나타내고, done은 순회 완료 여부를 나타내는 boolean 값입니다.
여기서 Symbol에 대해서 먼저 알면 이터레이션 프로토콜에 대한 이해가 더 쉽습니다. Symbol은 ES6에서 도입된 원시 타입으로, 이터레이션 프로토콜의 핵심인 Symbol.iterator와 같은 내장 심볼들을 통해 객체의 특별한 동작을 정의할 수 있기 때문입니다.
Symbol과 Global Symbol Resgistry
Symbol
Symbol 값은 Symbol 함수를 호출하여 생성합니다. 심볼 값은 자바스크립트 런타임 환경에서 Symbol 함수에 의해 동적으로 생성되며 다른 값과 중복되지 않는 고유한 값입니다. 즉, 변경 불가능한 원시 타입의 값입니다.
모든 Symbol() 호출은 각각 고유한 심볼을 반환하는 것이 보장됩니다. 주어진 키를 가진 심볼이 전역 심볼 레지스트리에 존재하면 그 심볼을 반환하고, 없으면 새로 생성한 후 전역 심볼 레지스트리에 등록하고 반환합니다.
Symbol 함수에 들어가는 문자열 인자는 심볼 값에 대한 Description으로서 선택적으로 넣을 수 있습니다. 이 문자열은 디버깅 용도로만 사용되며 심볼 값 생성에 영향을 주지 않습니다. 심볼 값도 객체처럼 메소드를 사용하면 암묵적으로 래퍼 객체를 생성합니다. 여기서 말하는 래퍼 객체란 일시적으로 임시 객체를 만들어서 메소드나 프로퍼티를 사용할 수 있게 한 후, 역할이 끝나면 다시 원시 값으로 돌아오는 것을 말합니다. 사용이 끝난 래퍼 객체를 GC의 대상이 됩니다. Description 프로퍼티와 toString은 Symbol.prototype의 프로퍼티이고, 심볼 값은 문자열이나 숫자 타입으로 변환되지 않습니다. 단, boolean 타입으로는 변환이 됩니다.
const a = Symbol('a')
a.description // "a"
a.toString() // "Symbol(a)"
!a // false
a + 'hi' // Uncaught TypeError
Global symbol registry
자바스크립트 엔진이 관리하는 전역 심볼 레지스트리는 사용 가능한 모든 심볼이 저장되어 있습니다. 레지스트리에 접근할 수 있는 함수로는 Symobl.for, Symbol.keyFor 메소드가 있습니다. 이 메소드들은 전역 심볼 레지스트리 테이블과 런타임 환경 사이에서 심볼 값을 전해주는 역할을 합니다. 전역 심볼 레지스트리는 대부분 자바스크립트 컴파일 인프라에 내장되어 있고, 레지스트리 내용은 자바스크립트 런타임 환경에서는 Symbol.for, Symbol.keyFor 메소드를 사용하지 않고서는 접근이 불가능합니다.
Symbol.for
Symbol.for 메소드는 인수로 전달받은 문자열을 키로 사용해 전역 심볼 레지스트리에 해당 키와 일치하는 심볼 값을 검색하게 됩니다. 이미 심볼이 있으면 해당 심볼을 반환하고, 없으면 새로 생성하여 반환합니다.
console.log(Object.getOwnPropertySymbols(Array.prototype));
console.log(Array[Symbol.species] === Array);
// [ Symbol(Symbol.iterator), Symbol(Symbol.unscopables) ]
// true
const symbolA:any = Symbol('A'); // 매번 새로운 고유한 symbol 생성, 전역 레지스트리와 관계 없이 항상 새로운 값.
const symbolB:any = Symbol('A');
console.log(symbolA === symbolB); // false, 서로 다른 고유한 symbol 값을 가짐
const symbolC:any = Symbol.for('A'); // 전역 레지스트리 검색 -> 없음 -> 생성 -> 저장 후 반환.
const symbolD:any = Symbol.for('A'); // 레지스트리 검색 -> 있음 -> 반환.
console.log(symbolC === symbolD) // true, 같은 symbol 참조
// false
// true
Symbol.keyFor
symbol.keyFor 메소드는 심볼 값을 인수로 받아서 전역 레지스트리에 저장된 심볼 키 값을 가져옵니다.
console.log(Symbol.keyFor(symbolC));
// A
Symbol 함수는 호출될 때 마다 심볼 값을 생성하지만 전역 심볼 레지스트리에 등록되어 관리되지 않고, Symbol.for 메소드를 사용하면 문자열 키를 통해 전역에서 중복되지 않는 심볼 값을 하나만 생성하여, 레지스트리를 통해 값을 공유할 수 있습니다. 정리하자면, Symbol()은 정말 고유한 값이 필요할 때 사용하고, Symbol.for()는 공유와 재사용이 필요할 때 사용한다고 보면 됩니다.
이터러블 프로토콜
1. 이터러블은 Symbol.iterator 메소드를 구현하거나 프로토타입 체인에 의해 상속한 객체이다.
const arr = [1,2,3];
console.log(Symbol.iterator in arr);
2. 이터러블 프로토콜을 준수한 배열은 for...of 문에서 순회 가능하다.
for(const item of arr) {
console.log(item);
}
3. 스프레드 연산자를 사용 가능하다.
const arr1 = [1,2,3];
const arr2 = [4,5,6];
// 복사
const copyArr = [...arr1]; // [1,2,3]
// 배열 결합
const combiedArr = [...arr1, ...arr2]; // [1,2,3,4,5,6]
// 중간에 값 추가
const newArr = [...arr1, 7, ...arr2]; // [1,2,3,7,4,5,6]
이터레이터 프로토콜
next()
이터레이터 프로토콜은 next 메소드를 소유하고, next 메소드를 호출하면 이터러블을 순회하며 value, done 프로퍼티를 갖는 이터레이터 리절트 객체 (IteratorResult) 를 반환합니다.
const array = [1,2,3];
// array[Symbol.iterator] 는 배열의 `Symbol.iterator`라는 내장 메서드에 접근하는 것.
const iter = array[Symbol.iterator]();
let iterResult = iter.next();
console.log(iterResult)
iterResult = iter.next();
console.log(iterResult)
iterResult = iter.next();
console.log(iterResult)
iterResult = iter.next();
console.log(iterResult)
// result:
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: undefined, done: true }
사용자 정의 이터러블
Symbol.iterator 메소드를 구현하여 자신만의 이터러블을 만들 수 있습니다.
const customIterable = {
[Symbol.iterator]() {
let count: number = 1;
return {
next() {
return count <= 3
? {value: count++, done: false}
: {value: undefined, done: true};
}
}
}
}
for(const num of customIterable) {
console.log(num);
}
// 1
// 2
// 3
지금까지 기본적인 이터러블, 이터레이터 프로토콜에 대해 알아보았습니다. 자바스크립트를 처음 접하면서 자바스크립트 언어의 유연한 특성이 너무 혼란스러웠습니다. 하지만 사람은 적응하는 동물아니겠습니까!!!! 타입스크립트를 자유자재로 사용하는 그 날 까지 열심히 공부해보려고 합니다. 파이팅!!!!!! (이펙티브 타입스크립트 책도 구매했습니다 ㅎㅎㅎㅎ)
참고 자료
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Symbol.iterator
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Symbol
- https://roseline.oopy.io/dev/javascript-back-to-the-basic/symbol-usage
- https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type