본 포스트는 인프런의 함수형 프로그래밍과 JavaScript ES6+ 강의(링크)를 듣고 정리한 내용입니다.
const products = [
{ name: "반팔티", price: 15000 },
{ name: "긴팔티", price: 20000 },
{ name: "핸트폰케이스", price: 15000 },
{ name: "후트티", price: 30000 },
{ name: "바지", price: 25000 },
];
let names = [];
for (const p of products) {
names.push(p.name);
}
console.log(names); //[ '반팔티', '긴팔티', '핸트폰케이스', '후트티', '바지' ]
let prices = [];
for (const p of products) {
prices.push(p.price);
}
console.log(prices); //[ 15000, 20000, 15000, 30000, 25000 ]
map
함수는 위에 코드와 같이 1대1로 매핑된 값을 수집하는 역할을 한다.함수형 프로그래밍에서는 함수가 인자와 리턴값으로 소통하는 것을 권장한다.
const map = () => { let names = []; for (const p of products) { names.push(p.name); } //console.log(names); return names; };
위에 코드에서
console.log(names);
는 함수의 외부영역에다 직접적인 영향이 미친다. 함수형 프로그래밍에서는 함수가 인자와 리턴값으로 소통하는 것을 권장하므로names
를 외부에 영향을 미치는 함수나 메서드로 전달하는 것이 아니라 결과를 리턴하여 리턴된 값을 개발자가 그 이후에 사용하는 것이 좋다.
const 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.name, products)); //[ 15000, 20000, 15000, 30000, 25000 ]
map
함수를 구현해 보았다.f
와 이터러블 iter
를 받는다.console.log(document.querySelectorAll('*').map(el => el.nodeName));
//document.querySelectorAll(...).map is not a function
console.log(map(el => el.nodeName, document.querySelectorAll('*')));
//['HTML', 'HEAD', 'META', 'TITLE', 'STYLE', 'CUSTOM-STYLE', 'STYLE', 'CUSTOM-STYLE', 'STYLE', 'CUSTOM-STYLE', 'STYLE', 'CUSTOM-STYLE', 'STYLE', 'STYLE', 'IRON-ICONSET-SVG', …]
const it = document.querySelectorAll('*')[Symbol.iterator]();
console.log(it.next()); //{value: html.focus-outline-visible, done: false}
console.log(it.next()); //{value: head, done: false}
console.log(it.next()); //{value: meta, done: false}
console.log(it.next()); //{value: title, done: false}
document.querySelectorAll
은 Map
함수(default)가 내부에 구현되어 있지 않다.map
함수(maked)로는 순회가 가능하다.document.querySelectorAll
도 이터러블/이터레이터 프로토콜을 따르기 때문!!function *gen() {
yield 2;
yield 3;
yield 4;
}
console.log(map((a) => a * a, gen())); //[ 4, 9, 16 ]
map
함수(maked)를 쓸 수 있다.let m = new Map();
m.set("a", 10);
m.set("b", 20);
const it = m[Symbol.iterator]();
console.log(it.next()); //{ value: [ 'a', 10 ], done: false }
console.log(it.next()); //{ value: [ 'b', 20 ], done: false }
console.log(it.next()); //{ value: undefined, done: true }
Map
(default)도 이터러블/이터레이터 프로토콜을 따른다.console.log(map(([k, a]) => [k, a * 2], m)); //[ [ 'a', 20 ], [ 'b', 40 ] ]
console.log(new Map(map(([k, a]) => [k, a * 2], m))); //Map(2) { 'a' => 20, 'b' => 40 }
Map
(default)과 map
(maked) 모두 이터러블/이터레이터 프로토콜을 따르기 때문에 Map
(default)의 인자로 map
(maked)를 넣을 수 있고, 그 반대도 가능하다.let under20000 = [];
for (const p of products) {
if (p.price < 20000) under20000.push(p);
}
console.log(...under20000);
//{ name: '반팔티', price: 15000 } { name: '핸트폰케이스', price: 15000 }
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));
//{ name: '반팔티', price: 15000 }, { name: '핸트폰케이스', price: 15000 }
console.log(...filter((p) => p.price >= 20000, products));
//{ name: '긴팔티', price: 20000 } { name: '후트티', price: 30000 } { name: '바지', price: 2
5000 }
filter
함수를 구현해 보았다.f
와 이터러블 iter
를 받는다.console.log(filter(n => n % 2, function *() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}))
//[ 1, 3, 5 ]
filter
함수 역시 map
함수 처럼 이터러블/이터레이터 프로토콜을 따르기 때문에 거의 모든 것들에 사용 가능하다.const nums = [1, 2, 3, 4, 5];
let total = 0;
for (const n of nums) {
total += n;
}
console.log(total); //15
reduce
함수는 값을 축약해 주는 역할을 한다.const add = (a, b) => a + b;
add
라는 함수가 있다고 할 때, add
를 통해 reduce
를 구현해 본다면 다음과 같은 형태가 될 것이다.add(add(add(add(0, 1), 2), 3), 4)
//15
reduce
함수의 내부에는 재귀적인 반복 구조가 들어가야된다.const reduce = (f, acc, iter) => {
for (const a of iter) {
acc = f(acc, a);
}
return acc;
};
reduce
함수를 구현해 보았다.f
와 누적 값 acc
그리고 이터러블 iter
를 받는다.reduce
는 acc
를 생략해도 동작을 하기때문에 바꿔야한다.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;
};
console.log(reduce(add, 0, [1, 2, 3, 4, 5])); //15
console.log(reduce(add, [1, 2, 3, 4, 5])); //15
acc
의 값의 존재 여부를 판별할 if문을 넣어준다.acc
의 존재 여부는 3번쩨 인자인 iter
의 존재 여부로 판별한다.acc
가 없을 경우, 들어온 acc
가 iter
이다.iter
의 값을 받지 않았기 때문에 iter
에 acc
(iter)의 이터레이터를 넣어준다.iter.next()
(iter
내부의 첫 값)을 acc
에 할당 해준다. console.log(
reduce(
add,
map(
(p) => p.price,
filter((p) => p.price < 20000, products)
)
)
);
//30000
console.log(
reduce(
add,
filter(
(n) => n >= 20000,
map((p) => p.price, products)
)
)
);
//75000