섹션 1. 함수형 자바스크립트 기본기
평가와 일급
평가란?
코드가 계산(Evaluation) 되어 값을 만드는 것
일급이란?
값으로 다룰 수 있는 것.
변수에 담을 수 있는 것.
함수의 인자로 사용될 수 있는 것.
함수의 결과로 사용될 수 있는 것.
const a = 10; // 변수에 담을 수 있다. , 값으로 다룰 수 있다.
const add10 = (a) => a + 10; // 함수의 인자로 사용될 수 있다. , 함수의 결과로 사용될 수 있다.
const b = add10(a); // 변수에 담을 수 있다.
console.log(add10(a));
일급 함수
함수를 값으로 다룰 수 있다.
조합성과 추상화의 도구
const add5 = (a) => a + 5; // 함수를 값으로 다룰 수 있다.
const f1 = () => () => 1; // 함수가 결과 값으로 사용될 수 있다.
const f2 = f1();
고차 함수
함수를 값으로 다루는 함수
함수를 인자로 받아서 실행하는 함수
const apply1 = (f) => f(1); // 함수를 인자 값으로 다루고 있다.
const add2 = (a) => a + 2;
console.log(apply1(add2));
const times = (f, n) => {
let i = -1;
while (++i < n) f(i);
};
함수를 만들어 리턴하는 함수 ( 클로저를 만들어 리턴하는 함수 )
const addMaker = (a) => (b) => a + b; // a 를 계속 기억하고 있다.
const add10 = addMaker(10);
console.log(add10(5));
console.log(add10(10));
섹션 2. ES6에서의 순회와 이터러블
기존과 달라진 ES6에서의 리스트 순회
ES5에서의 순회
배열의 length를 받아 해당 길이만큼 순회 해준다.
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]);
}
ES6에서의 순회
for (const a of list) {
console.log(a);
}
for (const a of str) {
console.log(a);
}
Array, Set, Map을 통해 알아보는 이터러블/이터레이터 프로토콜
이터러블 / 이터레이터 프로토콜이란?
이터러블 : 이터레이터를 리턴하는 [Symbol.iterator]()를 가진 값
이터레이터 : {value, done} 객체를 리턴하는 next()를 가진 값
이터러블/ 이터레이터 프로토콜 : 이터러블을 for…of, 전개 연산자 등과 함께 동작하도록 한 규약.
Array를 통해 알아보기
배열 내부에 Symbol.iterator 속성이 내장되어있다.for…of
문은 [Symbol.iterator]() 메소드를 호출하여 이터레이터 객체를 얻은 후, 순차적으로 next() 메소드를 호출하면서 하나씩 순회하는 것 이다.
따라서 iterator를 null로 바꿔버리면 for…of
문을 사용할 수 없다.
const arr = [1, 2, 3];
console.log(arr[Symbol.iterator]); // f values() { [native code] }
for (const a of arr) console.log(a); // 1 2 3
아래의 예시는 next() 메소드를 한번 호출하였기에 for…of 문에서 value 가 2부터 시작한다.
const arr = [1, 2, 3];
let iter1 = arr[Symbol.iterator]();
console.log(iter1.next()); // {value: 1, done: false}
for (const a of iter1) console.log(a); // 2 3
Set을 통해 알아보기
const set = new Set([1, 2, 3]);
// index로 접근은 불가는 하나 for문을 통해 순회는 가능
// for문을 통해 순회할 때는 Symbol.iterator를 통해 순회
console.log(set[0], set[1], set[2]); // undefined undefined undefined
for (const a of set) console.log(a); // 1 2 3
Map을 통해 알아보기
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
for (const a of map.keys()) console.log(a); // a b c
for (const a of map.values()) console.log(a); // 1 2 3
for (const a of map.entries()) console.log(a); // ['a', 1] ['b', 2] ['c', 3]
이터러블/이터레이터 프로토콜
- 이터러블: 이터레이터를 리턴하는 [Symbol.iterator]() 를 가진 값
- 이터레이터: { value, done } 객체를 리턴하는 next() 를 가진 값
- 이터러블/이터레이터 프로토콜: 이터러블을 for...of, 전개 연산자 등과 함께 동작하도록한 규약
사용자 정의 이터러블을 통해 알아보기
Symbol.iterator는 next() 메서드를 갖고 있으며 value와 done 객체를 return 해준다.
Symbol.iterator는 자기 자신을 return 해야한다.
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();
for (a of iterator) console.log(a);
console.log(iterator === iterator[Symbol.iterator]()); // true => Symbol.iterator는 자기 자신을 return하기 때문
브라우저에서 사용되는 dom과 관련된 여러 값들도 이터레이터 프로토콜을 따르고 있다.
for (const a of document.querySelectorAll("*")) log(a);
const all = document.querySelectorAll("*");
let iter3 = all[Symbol.iterator]();
log(iter3.next());
log(iter3.next());
log(iter3.next());
전개 연산자
const a = [1, 2];
// a[Symbol.iterator] = null; 을 실행할 경우 iterable 에러가 발생한다.
log([...a, ...arr, ...set, ...map.keys()]);
섹션 3. 제너레이터와 이터레이터
제너레이터와 이터레이터
제너레이터란?
이터레이터이자 이터러블을 생성하는 함수.
제너레이터를 통해 어떤 값도 순회하게 만들 수 있음
문장을 통해 순회할 수 있는 값을 생성한다.
return 값은 done 이 true가 되었을 때 반환하는 value 값이며 순환할 때 return 값은 포함되지 않는다.
function* gen() {
yield 1;
yield 2;
yield 3;
return 100;
}
let iter = gen();
console.log(iter[Symbol.iterator]() === iter); // true
for (a of iter) console.log(a); // 1 2 3 ( return 값 포함되지 않음 )
odds 제너레이터
홀수만을 뽑아 출력해보자
function* infinity(i = 0) {
while (true) yield i++;
}
function* limit(l, iter) {
for (const a of iter) {
yield a;
if (a === l) return;
}
}
function* odds(l) {
for (const a of limit(l, infinity(1))) {
if (a % 2) yield a;
}
}
let iter2 = odds(40);
for (const a of iter2) console.log(a);
for...of, 전개 연산자, 구조 분해, 나머지 연산자
console.log(...odds(10));
console.log([...odds(10), ...odds(20)]);
console.log(...odds(5));
const [head, ...tail] = odds(5);
console.log(head);
console.log(...tail);
const [a, b, ...rest] = odds(10);
console.log(a);
console.log(b);
console.log(rest);
섹션 4. map, filter, reduce
map
map 의 구성
const products = [
{ name: "반팔티", price: 15000 },
{ name: "긴팔티", price: 20000 },
{ name: "핸드폰케이스", price: 15000 },
{ name: "후드티", price: 30000 },
{ name: "바지", price: 25000 },
];
const map = (f, iter) => {
let res = [];
for (const a of iter) {
res.push(f(a));
}
return res;
};
console.log(map((p) => p.name, products));
이터러블 프로토콜을 따른 map의 다형성
위의 예시를 보면
querySelectorAll
은 순회 가능할 것 같으나querySelectorAll
을 자세히 살펴보면 map 메서드가 없다.
따라서undefined
를 내보내게 된다.
document.querySelectorAll('*').map(el => el.nodeName); // Error
console.log(document.querySelectorAll('*').map); // undefined
하지만
document.querySelectorAll('*')
는 이터러블 프로토콜을 따르고 있고, 위에서 생성한 map 함수는 이터러블 프로토콜을 따르는for-of
구문을 사용하기 때문에querySelectorAll
을 순회할 수 있다.
console.log(map((el) => el.nodeName, document.querySelectorAll("*"))); // ['HTML', 'HEAD', 'META', ...]
마찬가지로 제너레이터 함수를 통해 만들어진 이터러블 또한 map을 돌릴 수 있다.
function* gen() {
yield 2;
if (false) yield 3;
yield 4;
}
log(map((a) => a * a, gen())); // [4, 16]
filter
filter의 구성
const filter = (f, iter) => {
let res = [];
for (const a of iter) {
if (f(a)) res.push(a);
}
return res;
};
console.log(...filter((p) => p.price < 20000, products));
console.log(...filter((p) => p.price >= 20000, products));
console.log(filter((n) => n % 2, [1, 2, 3, 4])); // [1, 3]
제너레이터 함수를 통해 만들어진 이터러블 또한 filter에 적용할 수 있다.
console.log(
filter(
(n) => n % 2,
(function* () {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
})()
)
); // [1, 3, 5]
reduce
reduce의 구성
const reduce = (f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
// 1번째 값으로 초기화
acc = iter.next().value;
}
for (const a of iter) {
acc = f(acc, a);
}
return acc;
};