유인동님의 함수형 프로그래밍과 JavaScript ES6+ 인프런 강의를 듣고 개인적으로 정리한 내용입니다. 함수형 프로그래밍과 JavaScript ES6 이터레이터 프로토콜에 대해서 설명한다.
기존과 달라진 ES6에서의 리스트 순회 ES5 이하 버젼 1 2 3 4 5 6 7 8 9 const list = [1 , 2 , 3 ];for (var i = 0 ; i < list.length; i++) { console .log(list[i]); } const str = "abc" ;for (var i = 0 ; i < str.length; i++) { console .log(str[i]); }
console
ES5 이하의 버젼에서는 변수(list, str)에 담긴 값에 길이를 구하기위해서 length
라는 프로퍼티를 이용해서 리스트 순회를 적용 하였다.
ES6 1 2 3 4 5 6 7 8 9 const list = [1 , 2 , 3 ];for (const a of list) { console .log(a); } const str = "abc" ;for (const a of str) { console .log(a); }
console
ES6에서는 ES5이하보다 더 선언적으로 for…of 문을 이용해서 순회한다. 코드를 간결하게 해주는 것뿐만 아니라 for…of 문에 대해서 어떻게 추상화를 했는지에 대해서는 더 알아보자.
Array, Set, Map을 통해 알아보는 이터러블/이터레이터 프로토콜 Array을 통해 알아보기 1 2 3 4 const arr = [1 , 2 , 3 ];for (const a of arr) { console .log(a); }
console
1 2 3 4 5 const arr = [1 , 2 , 3 ];arr[Symbol .iterator] = null ; for (const a of arr) { console .log(a); }
console 1 > Uncaught TypeError : arr is not iterable
Symbol
는 ES6에서 추가된 어떤 객체에 대해서 Key로 사용될 수 있다. 여기서 arr[Symbol.iterator] = null;
코드를 추가하면 결과에서 에러가 나타나는 것을 확인할 수 있다. 즉, Symbol.iterator
가 for…of 문에 영향을 준다는 것을 알 수 있다. (Array, Set, Map 동일)
Set을 통해 알아보기 1 2 3 4 const set = new Set([1, 2, 3]);for (const a of set ) { console .log(a); }
console 1 2 3 4 5 6 7 8 9 > 1 > 2 > 3 < set [Symbol.iterator] > set [Symbol.iterator] length = 0 name = "values" [[Scopes]] = Scopes[0] __proto__ = function () { [native code] }
set[Symbol.iterator]
안에 구현되어있는 함수 확인
Map을 통해 알아보기 1 2 3 4 const map = new Map ([["a" , 1 ], ["b" , 2 ], ["c" , 3 ]]);for (const a of map) { console .log(a); }
console 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 > Array (2 ) 0 = "a" 1 = 1 length = 2 __proto__ = Array (0 ) > Array (2 ) 0 = "b" 1 = 2 length = 2 __proto__ = Array (0 ) > Array (2 ) 0 = "c" 1 = 3 length = 2 __proto__ = Array (0 ) < map[Symbol .iterator] > map[Symbol .iterator] length = 0 name = "entries" [[Scopes]] = Scopes[0 ] __proto__ = function ( ) { [native code] }
map[Symbol.iterator]
안에 구현되어있는 함수 확인
이터러블/이터레이터 프로토콜
이터러블: 이터레이터를 리턴하는 [Symbol.iterator]()
를 가진 값
이터레이터: { value, done } 객체를 리턴하는 next() 를 가진 값
console 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 > let iterator = arr[Symbol .iterator](); < iterator.next(); > iterator.next(); done = false value = 1 __proto__ = Object {constructor : , __defineGetter__ : , __defineSetter__ : , hasOwnProperty : , __lookupGetter__ : , ...} < iterator.next(); > iterator.next(); done = false value = 2 __proto__ = Object {constructor : , __defineGetter__ : , __defineSetter__ : , hasOwnProperty : , __lookupGetter__ : , ...} < iterator.next(); >iterator.next(); done = false value = 3 __proto__ = Object {constructor : , __defineGetter__ : , __defineSetter__ : , hasOwnProperty : , __lookupGetter__ : , ...} < iterator.next(); > iterator.next(); done = true value = undefined __proto__ = Object {constructor : , __defineGetter__ : , __defineSetter__ : , hasOwnProperty : , __lookupGetter__ : , ...}
이터러블/이터레이터 프로토콜: 이터러블을 for…of, 전개 연산자 등과 함께 동작하도록한 규약
위에 console 에서 value
에 들어오는 값을 담아서 출력을 해주는데 done = true
가 되면 for…of 에서 빠져 나오게 되어있다.
Array 코드로 이터레이터 다시 알아보기 1 2 3 4 5 6 const arrIter = [1 , 2 , 3 ];let iter1 = arrIter[Symbol .iterator]();iter1.next(); for (const a of iter1) { console .log(a); }
console
Symbol.iterator
를 실행한 arrIter는 iter1.next()
를 호출하고 그다음에 실행할 iter1의 value
값을 for…of의 변수 a에 담아 출력한다. 여기서 알 수 있는 게 for…of 안에서 Symbol.iterator
에 next()
함수가 호출되는 것을 알 수 있다. (Set, Map도 동일)
Map에만 있는 이터레이터 확인 1 2 3 4 5 6 7 8 9 10 const mapIter = new Map ([['a' , 1 ], ['b' , 2 ], ['c' , 3 ]]);for (const a of mapIter.keys()) { console .log(a); } for (const a of mapIter.values()){ console .log(a); } for (const a of mapIter.entries()) { console .log(a); }
console 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 > a > b > c > 1 > 2 > 3 > Array (2 ) 0 = "a" 1 = 1 length = 2 __proto__ = Array (0 ) > Array (2 ) 0 = "b" 1 = 2 length = 2 __proto__ = Array (0 ) > Array (2 ) 0 = "c" 1 = 3 length = 2 __proto__ = Array (0 )
사용자 정의 이터러블을 통해 알아보기 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const iterable = { [Symbol .iterator]() { let i = 3 ; return { next() { return i == 0 ? {done : true } : {value : --i, done : false }; } } } }; let iterator = iterable[Symbol .iterator]();console .log(iterator.next());console .log(iterator.next());console .log(iterator.next());console .log(iterator.next());
console 1 2 3 4 5 6 7 8 9 10 11 12 13 > {value : 2 , done : false } done: false value: 2 __proto__: Object > {value : 1 , done : false } done: false value: 1 __proto__: Object > {value : 0 , done : false } done: false value: 0 __proto__: Object > {done : true }
여기서 iterator변수에 [Symbol.iterator]
가 들어가있기때문에 iterator변수는 for…of 문에 들어갈수 있는 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const iterable = { [Symbol .iterator]() { let i = 3 ; return { next() { return i == 0 ? {done : true } : {value : --i, done : false }; } } } }; for (const a of iterable) { console .log(a); }
console
그리고 iterable이 이터레이터가되게 만들기위해서는 [Symbol.iterator]()
를 반환을 해야된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const iterable = { [Symbol .iterator]() { let i = 3 ; return { next() { return i == 0 ? {done : true } : {value : i--, done : false }; }, [Symbol .iterator]() { return this ; } } } }; let iterator = iterable[Symbol .iterator]();iterator.next(); iterator.next(); for (const a of iterator) { console .log(a); }
console
전개 연산자 전개 연산자는 배열을 더욱더 직관적으로 사용 할 수 있고, 배열을 분해 하지 않고 그냥 할당 해 버리는 방식이다. 즉, 배열 중간에 배열 값을 할당하기 위해서 많이 사용된다.
1 2 const a = [1 , 2 ];console .log([...a, ...arr, ...set, ...map.keys()]);
console 1 2 3 4 5 6 7 8 9 10 11 12 13 14 > (11 ) [1 , 2 , 1 , 2 , 3 , 1 , 2 , 3 , "a" , "b" , "c" ] 0 : 1 1 : 2 2 : 1 3 : 2 4 : 3 5 : 1 6 : 2 7 : 3 8 : "a" 9 : "b" 10 : "c" length: 11 __proto__: Array (0 )
ES6에서의 순회와 이터러블:이터레이터 프로토콜 소스코드
참조