1. 함수형 자바스크립트 기본기
스터디 소개
아름다운 코드는 짧고 간결해야 한다.
ES6+에 대한 깊은 이해가 있으신 분
을 요구하는 경우가 많다.ES6+
평가와 일급
const a = 10;
const add10 = a => a+10;
const r = add10(a);
log(r); // 20
일급 함수
const add5 = a => a+5;
log(add5); //a => a+5
log(add5(5)); // 10
const f1 = () => () => 1;
log(f1()); // () => 1
const f2 = f1();
log(f2); // () => 1
log(f2()); // 1
고차함수
const apply1 = f => f(1);
const add2 = a => a+2;
console.log(apply1(add2)); //3
console.log(apply1(a=>a-1)); //0
const times = (f,n) => {
let i = -1;
while(++i < n) f(i);
};
times(console.log, 3); // 0 1 2
times(a => console.log(a + 10),3)
//10 11 12
함수를 만들어 리턴하는 함수(클로저를 만들어 리턴하는 함수)
const addMaker = a => b => a + b; //클로저를 리턴하는 함수
const add10 = addMaker(10);
console.log(add10(5)); // 15
console.log(add10(10)); // 20
//클로저는 내부함수가 외부함수의 맥락에 접근할 수 있는 것을 가르 킨다.
// 내부함수(addMaker 10 => a + 10 을 리턴)가 외부함수의(add10(5)에서 5가 외부함수의 맥락이다.) 맥락에 접근한다.
2. ES6에서의 순회와 이터러블:이터레이터 프로토콜
리스트 순회
const list = [1,2,3];
for(var i=0; i<list.length; i++){
console.log(list[i]);
}
// ->
for(const a of list){
console.log(a);
}
Array, Set, Map을 통해 알아보는 이터러블/이터레이터 프로토콜
const arr = [1,2,3];
console.log(arr[Symbol.iterator]);
// 결과(f values()) array는 Symbol.iteraotr를 갖고 있고
let iterator = arr[Symbol.iterator]();
for(let a of iterator) console.log(a); // 1,2,3
/*
* Object done: false value: 1[[Prototype]]: Object
Object done: false value: 2[[Prototype]]: Object
Object done: false value: 3[[Prototype]]: Object
*/
전개연산자
const a = [1,2];
console.log([...a,...[3,4]]);
//1234
//console.log([...a,...arr,...set,...map.keys()]);
//iterator 프로토콜을 따르고 있는 것.
재너레이터
function *
이 필요합니다. function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1, 2가 출력됨
}
function *gen() {
yield 1;
if (falses) yield 2;
yield 3;
}//재너레이터 : 순회할 수 있는 값이면 어떠한 값이든 이터러블을 만들 수 있다.
let iter = gen(); // 이터레이터를 리턴하는 이터러블 만들기
console.log(iter.next()); // value: 1, done: false}
console.log(iter[Symbol.iterator]()); // iterator는 자기자신을 리턴한다.
//**********************************//
function *infinity(i = 0){
while(true) yield i++;
}
function *limit(l, iter) {
for(const a of iter){
yield a;
if (a == l) return;
}
}
let iter1 = limit(4,[1,2,3,4,5,6]);
console.log(iter1.next()); //1
console.log(iter1.next()); //2
console.log(iter1.next()); //3
console.log(iter1.next()); //4
console.log(iter1.next()); //undefined
console.log("=========================")
function *odds(l) {
for(const a of limit(l,infinity(1))) {
if (a % 2 ) yield a;
}
}
let iter2 = odds(10);
console.log(iter2.next()); //1
console.log(iter2.next()); //3
console.log(iter2.next()); //5
console.log(iter2.next()); //7
console.log(iter2.next()); //9
console.log(iter2.next()); //undefined 쭉
console.log(...odds(10)); // 1 3 5 7 9
console.log([...odds(10), ...odds(20)]); // Array(15)
const[head, ...tail] = odds(5);
console.log(head); // 1
console.log(tail); // array(2) (3 5)
//재너레이터 코드를 순회해서 로직을 만들고 문장으로 만들어진 것을 순회하고 값으로 다룰 수 있다.
3. map, filter, reduce
map
<script type="text/javascript">
let products =[
{name: '반팔티', price: 15000},
{name: '긴팔티', price: 15000},
{name: '모자', price: 15000},
{name: '핸드폰케이스', price: 15000},
{name: '후드티', price: 15000},
];
</script>
<script>
let map = (f,iter) => {
let res = [];
for(const a of iter) {
res.push(f(a));
}
return res;
};
console.log(map(p => p.name, products));
console.log(map(p => p.price, products));
//함수형 프로그래밍으로 map 구현
// let names = [];
// for (const p of products) {
// names.push(p.name);
// }
// log(names);
// let prices = [];
// for (const p of products) {
// prices.push(p.price);
// }
// log(prices);
let m = new Map();
m.set('a',10);
m.set('b',20);
console.log(new Map(map(([k,a]) => [k,a*2],m))); // value 값에 2 곱해진 map 리턴
filter
<script type="text/javascript">
let products =[
{name: '반팔티', price: 15000},
{name: '긴팔티', price: 10000},
{name: '모자', price: 8000},
{name: '핸드폰케이스', price: 7000},
{name: '후드티', price: 12000},
];
</script>
<script type="text/javascript">
const filter = (f,iter) => {
let res = [];
for(const a of iter){
if(f(a)) res.push(a);
}
return res;
};
let under10000 = [];
for(const p of products) {
if(p.price < 10000) under10000.push(p);
}
console.log(...under10000);
console.log(...filter(p => p.price<10000, products));
</script>
reduce
const reduce = (f, acc, iter) => {
if(!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for(const a of iter) {
acc = f(acc,a);
}
return acc;
};
const add = (a,b) => a + b;
console.log(reduce(add,0,[1,2,3,4,5])); //15
console.log(reduce(add,[1,2,3,4,5])); // 15
console.log(add(add(add(add(add(0,1),2),3),4),5)); //15
console.log(
reduce(
add,
map(p => p.price,
filter(p => p.price < 20000, products))));
4. 코드를 값으로 다루어 표현력 높이기
go
const go = (...args) => reduce((a, f) => f(a), args);
go(
/* 0,
a => a + 1, */
add(0, 1),
a => a + 10,
a => a + 100,
console.log
//111
);
console.log(
reduce(
add,
map(p => p.price,
filter(p => p.price < 20000, products))));
//23500
go(
products,
products => filter(p => p.price < 20000, products),
products => map(p => p.price,products), a => reduce(add,a),
console.log
)
pipe
const pipe = (f,...fs) => (...as) => go(f(...as),...fs);
const f = pipe(
//a => a + 1,
(a,b) => a+b,
a => a + 10,
a => a + 100
);
console.log(f(0,1));
curry
const curry = f =>
(a, ..._) => _.length ? f(a,..._) : (..._) => f(a, ..._);
const multi = curry((a,b) => a*b);
console.log("curry ex : " + multi(3));
// curry ex : (a, ..._) => _.length ? f(a,..._) : (..._) => f(a, ..._)
console.log("curry ex : " + multi(3)(2));
go, pipe의 사용 이유는 각종 함수를 한 번에 묶고 가독성을 높일 수 있어서 사용한다.
5.장바구니 예제
//js.js
const log = console.log;
const curry = f =>
(a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
const map = curry((f, iter) => {
let res = [];
for (const a of iter) {
res.push(f(a));
}
return res;
});
const filter = curry((f, iter) => {
let res = [];
for (const a of iter) {
if (f(a)) res.push(a);
}
return res;
});
const reduce = curry((f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for (const a of iter) {
acc = f(acc, a);
}
return acc;
});
const go = (...args) => reduce((a, f) => f(a), args);
const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTML 출력해보기 - 장바구니</title>
<script src="./js.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH91sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</head>
<body>
<div id="cart">
</div>
<script>
const products = [
{name: '반팔티', price: 15000, quantity: 1, is_selected: true},
{name: '긴팔티', price: 20000, quantity: 2, is_selected: false},
{name: '핸드폰케이스', price: 15000, quantity: 3, is_selected: true},
{name: '후드티', price: 30000, quantity: 4, is_selected: false},
{name: '바지', price: 25000, quantity: 5, is_selected: true}
];
const add = (a, b) => a + b;
const sum = curry((f, iter) => go(
iter,
map(f),
reduce(add)));
const total_quantity = sum(p => p.quantity);
const total_price = sum(p => p.price * p.quantity);
document.querySelector('#cart').innerHTML = `
<table class="table table-hover">
<tr class="table-info">
<th></th>
<th>상품 이름</th>
<th>가격</th>
<th>수량</th>
<th>총 가격</th>
</tr>
${go(products, sum(p => `
<tr class="table-light">
<td><input type="checkbox" ${p.is_selected ? 'checked' : ''}></td>
<td>${p.name}</td>
<td>${p.price}</td>
<td><input type="number" value="${p.quantity}"></td>
<td>${p.price * p.quantity}</td>
</tr>
`))}
<tr class="table-primary">
<td colspan="3">합계</td>
<td>${total_quantity(filter(p => p.is_selected, products))}</td>
<td>${total_price(filter(p => p.is_selected, products))}</td>
</tr>
</table>
`;
// 문자열이 배열에 있었는데 reduce를 통해서 하나의 문자열로 더해주게 되면
// console과 같이 테이블형태의 string이 생긴다.
//go를 사용해서 함수를 나열하고, pipe를 사용해서 함수를 묶고, curry는 메소드 참조를 할 수 있도록 해준다.
//총 수량
/* const total_quantity = pipe(
map(p => p.quantity),
reduce(add)) */
//총 합계
/* const total_price = pipe(
map(p => p.price),
reduce(add)); */
</script>
</body>
</html>