[JavaScript] 함수형 프로그래밍 (map, filter, reduce)

 

 

 

- 데이터

const products = [
  { name: '반팔티', price: 15000 },
  { name: '긴팔티', price: 20000 },
  { name: '핸드폰케이스', price: 15000 },
  { name: '후드티', price: 30000 },
  { name: '바지', price: 25000 }
];

 

 

# map

const map = (f, iter) => {
  let res = [];
  for (const a of iter) {
    names.push(f(a));
  }
  return res;
}

함수의 인자로 받은 iter를 통해 순회를 하고 새로운 배열의 형태를 만든다. 첫번째 인자 f는 이터러블의 어떤 값을 수집할 것인지 추상화한다. 그렇기때문에 받아온 함수(f)를 통해서 어떤 값을 수집할 것인지 f 함수에게 위임한다.

 

let names = [];
for (const a of products){
  names.push(p.name);
}

console.log(names); // ["반팔티", "긴팔티", "핸드폰케이스", "후드티", "바지"]
console.log(map(p => p.name, products)); // ["반팔티", "긴팔티", "핸드폰케이스", "후드티", "바지"]


let prices = [];
for (const a of products){
  prices.push(p.price);
}
console.log(prices); // [15000, 20000, 15000, 30000, 25000];
console.log(map(p => p.price, products)); // [15000, 20000, 15000, 30000, 25000];

함수형 프로그래밍에선 map이라는 함수의 보조함수를 통해서 이터러블 안에 있는 수집하고자 하는 어떠한 값을 수집할 수 있다. map 함수는 함수를 값으로 다루면서 원하는 시점에 인자를 적용하는 고차함수이다.

 

 

# 이터러블 프로토콜을 따른 map의 다형성

console.log([1, 2, 3].map(a => a + 1)); // [2, 3, 4]
console.log(document.querySelectorAll('*')); // undefined

[1, 2, 3]은 배열(Array)이고 이터러블이다. 하지만 querySelectorAll(NodeList)은 이터러블이긴 하지만 배열은 아니므로 Array.prototype을 상속하고 있지 않다. 따라서 map함수를 사용할 수 없다.

 

console.log(map(el => el.nodeName, document.querySelectorAll('*')));

const it = document.querySelectorAll('*')[Symbol.iterator]();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

querySelectorAll은 이터러블이므로 [Symbol.iterator]() 메서드를 가지며 next()메서드로 값을 순회할 수 있다.

 

function* gen(){
  yield 2;
  if(false) yield 3;
  yield 4;
}

console.log(map(a => a * a, gen())); // [4, 16]

querySelectorAll 말고도 이터러블을 만족하는 것이라면 위에서 만들어놓은 map함수를 사용할 수 있다. gen()이라는 제너레이터 함수 또한 map으로 순회 가능하다. 이터러블 프로토콜을 따르는 이미 만들어져있는 이터러블에 map을 사용할 수 있지만 gen() 제너레이터 함수의 결과(문장)에 대해서도 map을 사용하여 순회할 수 있다. 그렇다는 것은 사실상 모든것들에 대해 map을 할 수 있다.

 

앞으로 만들어지는 WEB API 자바스크립트를 포함한 브라우저에서 사용되는 값들(querySelectorAll) 또한 ECMAScript의 이터러블 프로토콜을 따르고 있기때문에 계속해서 이러한 형태로 만들어질 것이다. 이터러블 프로토콜을 따르는 함수를 사용하는 것은 앞으로의 많은 다른 함수와의 조합성이 좋아진다. 훨씬 더 유연하고 다형성이 있는 기법을 사용할 수 있을것이다.

 

- Map

let m = new Map();
m.set('a', 10);
m.set('b', 20);

console.log(map(([k, a]) => [k, a * 2], m)); // [ ["a", 20], ["b", 40] ]
console.log(new Map(map(([k, a]) => [k, a * 2], m))); // { "a" => 20, "b" => 40 }

처음에 만든 Map객체와 동일하게 내부적으로 로직처리를 하고 난 후(*2)의 새로운 Map 객체를 map함수를 통해서 만들 수 있다.

 

 

# filter

const filter = (f, iter) => {
  let res = [];
  for(const a of iter){
    if (f(a)) res.push(a);
  }
  return res;
}

map과 형태는 동일하다. 하지만 첫번째 인자로 받는 함수(f)를 가지고 어떤 조건을 가지고 로직을 처리할 것인지에 대해 체크하는 부분을 받아온 함수에 위임한다.

 

let under20000 = [];
for (const a of products){
  if(p.price < 20000) under20000.push(p);
}
console.log(...under20000); // { name: "반팔티", price: 15000 } { name: "핸드폰케이스", price: 15000 }

console.log(...filter(p => p.price < 20000, products)); // { name: "반팔티", price: 15000 } { name: "핸드폰케이스", price: 15000 }

let over20000 = [];
for(const a of products){
 if(p.price >= 20000) over20000.push(p);
}
console.log(...over20000); // { name: "긴팔티", price: 20000 } { name: "후드티", price: 20000 } { name: "바지", price: 25000 }

console.log(...filter(p => p.price >= 20000, products); // { name: "긴팔티", price: 20000 } { name: "후드티", price: 20000 } { name: "바지", price: 25000 }

내부에 있는 값에대한 다형성은 첫번째 인자로 받은 보조함수를 통해서 지원을 해주고 외부의 경우는 두번 째 인자로 받은 이터러블 프로토콜을 따르는 인자를 통해서 다형성을 지원해줄 수 있다.

 

console.log(filter(n => n % 2, [1, 2, 3, 4])); // [1, 3]
console.log(fitler(n => n % 2, function* () {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
}())); // [1, 3, 5]

filter함수 역시 이터러블 프로토콜을 따르고 함수형적인 코딩을 통해서 중복을 제거하고 구현할 수 있다.

 

 

# reduce

const reduce = (f, acc, iter) => {  
  for(const a of iter){
    acc = f(acc, a)
  }
  return acc;
}

reduce는 값을 축약하는 함수이다. 특정한 값들을 순회하면서 하나의 값으로 누적할 때 사용하는 함수이다. 

 

const add = (a, b) => a + b;

console.log(reduce(add, 0, [1, 2, 3, 4, 5]); // 15

// console.log(add(add(add(add(add(0, 1), 2), 3), 4), 5)); 형태로 실행된다.

reduce 함수는 첫번째 인자로 로직을 처리할 함수, 두번째 인자로 초기값, 세번째 인자로 이터러블을 받는다.

 

console.log(reduce(acc, [1, 2, 3, 4, 5]); // 15

초기 값을 전달하지 않아도 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;
}

위의 if문의 로직과 같이 reduce 함수의 파라미터의 초기값을 누락하고 호출하게되면 reduce 내부의 로직에서 acc에 들어온 이터러블의 [Symbol.iterator] 메서드를 실행하고 초기 값에 iter 이터러블의 next()를 실행한 값을 넘겨주기때문에 초기값이 없더라도 같은 값으로 실행된다.

 

console.log(reduce(
  (total_price, product) => total_price + product.price, 
  0, 
  products)); // 105000

reduce는 보조함수를 통해서 축약하는 것을 완전히 위임하기 때문에 숫자나 숫자 배열 뿐만아니라 객체 등의 복잡한 값들을 특정한 값으로 축약할 수 있다. reduce도 역시 보조함수를 통해서 안쪽에 있는 값의 다형성을 지원해주며 이터러블을 통해서 외부값의 다형성도 지원한다.

 

 

# map, filter, reduce

const add = (a, b) => a + b;

console.log(
  reduce(
    add, 
    map(p => p.price, 
      filter(p => p.price < 20000, products))));
      
console.log(
  reduce(
    add, 
    filter(n => n >= 20000, 
      map(p => p.price, products))));

map과 filter, reduce 함수가 중첩되어있는 함수이다. 이렇게 작성된 함수들은 뒤부터 읽으면 쉽게 읽을 수 있다. 함수를 작성할 때 내부에서 사용하려는 함수와 외부에서 평가될 이터러블의 값을 유추하면서 작성한다.

 

 

[JavaScript] 함수형 프로그래밍 (제너레이터, 이터레이터)

 

 

 

# 제너레이터 / 이터레이터

 - 제너레이터: 이터레이터이자 이터러블을 생성하는 함수

function* gen(){
  yield 1;
  if(false) yield 2;
  yield 3;
}

let iter = gen();
// console.log(iter[Symbol.iterator]() == iter); // true
console.log(iter.next()); // { value: 1, done: false } 
console.log(iter.next()); // { value: 3, done: false } 
console.log(iter.next()); // { value: undefined, done: true } 
console.log(iter.next()); // { value: undefined, done: true } 

for(const a of iter) console.log(a); // 1 3

제너레이터는 이터러블과 이터레이터의 조건을 만족한다. 따라서 제너레이터 함수의 객체를 이터러블을 만족하는 작업 없이도 for...of 문으로 순회 할 수 있다. 또한 제너레이터는 순회할 값을 문장(yield)으로 표현할 수 있다. 어떠한 값이든 이터러블이면 순회할 수 있고, 제너레이터는 문장을 값으로 만들수 있고 문장을 통해 순회할 수 있는 값을 만들 수 있기때문에 어떠한 상태나 어떠한 값이든 순회할 수 있도록 만들 수 있다.

 

이 점은 함수형 프로그래밍의 관점에서 중요하다. 자바스크립트는 다형성이 높다고 할 수있다. 제너레이터라는 문장을 통해 순회할 수 있는 값을 만들 수 있다는 것은 어떠한 값도 순회할 수 있는 형태로 동작할 수 있고 다양한 값을 순회하는 이터러블을 만들 수 있다.

 

 

# odds

function* infinity(i = 0){
  while(true) yield i++;
}

function* limit(l, iter){
  for (const a of iter){
    yield a;
    if(l == a) return;
  }
}

function* odds(l) {
  for (const a of limit(l, infinity(1))){
    if (a % 2) yield a;
  }
}

let iter = odds(10);
iter.next(); // { value: 1, done: false }
iter.next(); // { value: 3, done: false }
iter.next(); // { value: 5, done: false }
iter.next(); // { value: 7, done: false }
iter.next(); // { value: 9, done: false }
iter.next(); // { value: undefined, done: true }
iter.next(); // { value: undefined, done: true }

for(const a of odds(40)) console.log(a); // 1, 3, 5 ... 39

제너레이터를 이용해서 홀수만을 출력하는 함수를 만들었다. infinity() 제너레이터함수의 경우는 무한하는 while 문에서 i의 값을 1씩 증가시킨다. 겉으로 보기엔 함수를 실행하면 브라우저가 다운되겠지만 제너레이터 함수의 yield를 통해서 하나씩 값을 실행하고 실행을 중지 시키기때문에 그렇게 되지는 않는다.

 

limit() 제너레이터함수는 파라미터로 숫자와 이터러블을 만족하는 값을 받아온다. 받아온 이터러블을 순회하면서 인자로 받은 숫자와 이터러블의 값이 같아질 때 리턴하여 done: true를 반환한다.

 

odds() 제너레이터 함수는 limit() 제너레이터함수와 infinity() 제너레이터함수를 사용하여 홀수 값을 가져온다. limit과 infinity는 이터러블 프로토콜을 만족하므로 for...of를 순회하는 이터러블 값으로 사용될 수 있다.

 

 

# for of, 전개 연산자, 구조 분해 할당, 나머지 연산자

console.log(...odds(10)); // 1 3 5 7 9
console.log([...odds(10), ...odds(20)]); // [1, 3, 5, 7, 9, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

const [head, ...tail] = odds(5);
console.log(head); // 1
console.log(tail); // [3, 5]

const [a, b, ...rest] = odds(10);
console.log(a); // 1
console.log(b); // 3
console.log(rest); // [5, 7, 9]

자바스크립트에서는 이터러블 / 이터레이터 프로토콜을 가지고 활용할 수 있는 문법들과 기능들이 많다. 또한 많은 라이브러리나 함수들도 이터러블 프로토콜을 따르도록 구성이 되어있다면 제너레이터와 이터레이터를 활용하여 좀 더 조합성이 높은 프로그래밍을 할 수 있다.

[JavaScript] 함수형 프로그래밍 (이터러블, 이터레이터)

 

 

 

ES6부터 리스트를 순회하는 방법의 차이가 생겼다.

console.log('Array');
const arr = [1, 2, 3];
for(const a of arr) console.log(a); // 1 2 3

console.log('Set');
const set = new Set([1, 2, 3]);
for(const a of set) console.log(a); // 1 2 3

console.log('Map');
const map = ([['a', 1], ['b', 2], ['c', 3]]);
for(const a of map) console.log(a); // ["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, 전개 연산자, 구조 분해 할당 등과 함께 동작하도록 만든 규약

arr[Symbol.iterator] // f values() { [native Code] }

let iterator = arr[Symbol.iterator]();
iterator.next() // { value: 1, done: false} 
iterator.next() // { value: 2, done: false} 
iterator.next() // { value: 3, done: false} 
iterator.next() // { value: undefined, done: true}

 

 

# 사용자 정의 이터러블을 통해 알아보기

const iterable = {
  [Symbol.iterator](){
    let i = 3;
    return {
      next(){
        return (i === 0) ? {done: true} : { value: i--, done: false };
      }
    }
  }
}

let iterator = iterable[Symbol.iterator]();
iterator.next();
for(const a of iterable) console.log(a); // 2 1
for(const a of iterator) console.log(a); // TypeError: iterator is not iterable

이터러블 / 이터레이터 프로토콜을 사용하기위해 사용자가 정의할때는 위의 [Symbol.iterator]() 메서드를 정의하고 [Symbol.iterator]() 메서드는 next() 메서드를 반환해야하며 next() 메서드의 내용으로는 { value, done } 을 가지는 객체를 반환해야 한다.

 

하지만 위의 이터러블 / 이터레이터는 완벽하지 않다. Array나 Set, Map 등 다른 이터러블 프로토콜을 만족하는 것들의 내부를 보면 이터러블 프로토콜을 만족하면서 이터레이터 프로토콜을 만족한다. 그러한 이터러블 프로토콜을 well-formed 이터러블 프로토콜 이라고 한다.

 

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(const a of iterable) console.log(a); // 2 1
for(const a of iterator) console.log(a); // 2 1

well-formed 이터러블 프로토콜은 이터러블을 for...of 문으로 순회를 해도 순회가되고 이터레이터로 만들어서 for...of 문으로 순회해도 순회가 된다. 또한 일정부분 next() 메서드를 통해 진행을 하고 순회를 해도 마찬가지로 순회가 된다.

 

console.log(document.querySelectorAll('*')) // NodeList(...) [...]
for(const a of document.querySelectorAll('*')) console.log(a); // <html>...</html> <head>...</head> ...
const all = document.querySelectorAll('*');
let iter = all[Symbol.iterator]();
iter.next(); // { value: html, done: false }
iter.next(); // { value: head, done: false }
iter.next(); // { value: script, done: false }

해당하는 이터러블 프로토콜은 오픈소스 라이브러리 뿐만 아니라 브라우저의 DOM 또한 이터러블 프로토콜을 만족한다.

 

 

# 전개연산자

const a = [1, 2];
// a[Symbol.iterator] = null; // null이라면 아래 코드 TypeError
console.log([...a, ...arr, ...set, ...map.keys()]); // [1, 2, 1, 2, 3, 1, 2, 3, "a", "b", "c"]

자바스크립트에서 ES6의 이터러블 / 이터레이터 프로토콜은 중요하다. 해당하는 프로토콜을 만족하면 기존에 ES5에서 사용하던 자료구조보다 더 다양하고 쉬운 방식으로 데이터들을 핸들링 할 수 있다.

[JavaScript] 함수형 프로그래밍

 

 

 

# 평가

 - 코드가 계산되어 값으로 만들어지는 것

 

# 일급

 - 값으로 다룰 수 있다.

 - 변수에 담을 수 있다.

 - 함수의 인자로 사용될 수 있다.

 - 함수의 결과로 반환될 수 있다.

 

# 일급 함수

 - 함수를 값으로 다룰 수 있다.

 - 조합과 추상화의 도구

 

자바스크립트에서 함수는 일급이다. 변수에 함수를 값으로 담을 수 있고, 함수의 인자로 함수를 전달 할 수 있다. 또 함수를 평가해서 값으로 다른 함수에도 전달 할 수 있다.

const add5 = a => a + 5;
console.log(add5); // a => a + 5
console.log(add5(5)); // 10

const f1 = () => () => 1;
console.log(f1()); // () => 1

const f2 = f1();
console.log(f2); // () => 1
console.log(f2()); // 1

 

 

# 고차 함수

 - 함수를 값으로 다루는 함수

 

고차함수는 두가지로 나눠진다.

 

 

## 함수를 인자로 받아서 실행하는 함수

const apply1 = f => f(1);
const add2 = a => a + 2;
console.log(apply1(add2)); // 3 -> (a => a + 2)(1);
console.log(apply1(a => a - 1)); // 0

const times = (f, n) => {
  let i = -1;
  while(++i) 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); // b => a + b;
console.log(add10(5)); // 15
console.log(add10(10)); // 20

addMaker 함수는 함수를 리턴하는 함수이며, 클로저를 만들어 리턴한다. b => a + b 함수는 함수이자 a를 기억하는 클로저이다. 그러므로 addMaker는 클로저를 리턴하는 함수이고 함수가 함수를 만들어서 리턴할 때는 결국엔 클로저를 만들어서 리턴하는 방식으로 사용한다.

 

패스트캠퍼스 챌린지 최종후기

 

 

- 강의를 듣게 된 이유

 

이번에 패스트캠퍼스에서 김민태의 프론트엔드 아카데미: 제 1강 JavaScript & TypeScriptEssential 강의를 들어봤다. 처음 TypeScript에 관심이 생긴건 JavaScript를 자바처럼 클래스 객체지향 형식으로 사용하는 것을보고 관심이 생겼다. ES6에도 class를 사용할 수 있지만 이것보다 TypeScriptJavaScript에 타입도 지정하기 때문에 좀 더 기존에 알고있었던 자바와 가까운 느낌(?)이 들었기 때문에 관심있게 보다가 수강하게 되었다. 듣다보니 점점 TypeScript보다 JavaScript에 관심이 더 생겼다. TypeScript도 JavaScript의 슈퍼셋이기 때문에 결국엔 JavaScript를 잘 알아야 TypeScript도 잘 알수 있는것이다. 처음 강의를 보기 전과 후의 마음은 다르지만 결국엔 JavaScript나 TypeScript나 두가지 다 관심이 있기때문에 시작했다.

 

물론 지금까지도 전부 다 이해했다고 할 수는 없지만 강의를 들어보니 단순히 처음 시작할 때 알고 있었던 것보다 더 많은 것들을 알게되었다. (알면 알수록 더 어려운 듯한 느낌도 같이 오는...) 처음엔 JavaScript를 어느정도 사용할 줄 안다고 생각했고 (그냥 어느정도 자주 사용하는 DOM이나 웹 변경하는 기능만 아는 수준이었다) 거기에 추가하여 TypeScript를 좀 더 배우고 알게되면 좋겠다고 생각을 했는데 TypeScript 뿐만 아니라 JavaScript를 포함해서 여러가지를 더 알게 되었던거 같다.

 

 

- 강의를 듣고 알게된 점

 

강의 처음 소개할 때 우리는 웹앱을 만든다고 했다. 웹이면 웹이고 앱이면 앱인데 웹앱은 뭐지 ? 라는 생각을 했는데 웹을 모바일 앱처럼 (예를 들면 인스타그램 등) 화면의 동작이 부드러운 웹, 마치 동작이 앱처럼 동작하는 웹을 웹앱이라고 하는것같다. 그리고 이렇게 동작하는데 있어서 SPA를 구현해야 한다는데 내가 기존에 알고있었던 SPA는 React, Vue, Angular 등 JavaScript 라이브러리나 프레임워크를 사용해서 구현하는 것, 다시 말해서 위와같은 프레임워크를 사용하는 것이 SPA 그 자체인줄 알았다. 하지만 이런 라이브러리나 프레임워크도 JavaScript로 만들어진 것이기 때문에 그러한 기능을 사용하는데 좀 더 편하게 만들어 놓은것이고 그냥 바닐라 JavaScript를 사용해도 SPA를 구현할 수 있다는 것이다. 본 강의에서 구현하는것들은 SPA 방식으로 구현한다. 

 

이런 부분에 대해서 강의에 대해 좀 더 흥미가 생긴것 같다. 지금까지 내가 개발해 본 것들은 SSR 방식으로 페이지 하나당 하나의 html, 하나의 jsp를 생성해야했고 화면의 전환이나 어떤 기능의 동작이 발생했을 때, 화면을 새로 불러와야 했다. (물론 ajax를 사용해서 데이터를 받아오고 화면을 변경한 경우도 있기 때문에 꼭 그런 것만은 아니다) SSR 방식이던 CSR 방식이던 간에 어플리케이션에 맞고 어울리는 방식이 있을 것이다. 하지만 이 강의를 들으면서 CSR 방식을 가지고 SPA를 구현해 보는 것은 충분히 좋은 경험이 되었다.

 

또한 화면을 구현할 때, ES6의 class를 통해서 클래스를 통해 인스턴스를 만들어서 사용했다. JavaScript로 만들어 놓은 구조를 TypeScript로 후에 변경한다. 이 말은 처음 프로그래밍을 자바로 시작했던 나한테 관심을 생기게 만든 큰 이유이기도 했다. 클래스 객체지향 방식, 클래스 안에 필드와 메서드를 만들어서 여러개의 인스턴스를 통해 상태와 동작을 관리한다. 어플리케이션의 로직은 이러한 방식으로 관리하고 화면, UI 부분은 jsp의 <script> 태그 안에 제이쿼리의 ready 함수나 DOMContentLoaded 등을 사용해서 그 안에 각각 화면에 사용하는 부분만 만들어서 사용하는 경우가 허다했지만 JavaScript를 가지고 객체지향 형식으로 구조를 만들 수 있는 것은 앞서 말한대로 SPA를 구현할 수 있고 화면을 페이지 하나마다 재사용성 없는 복사 붙여넣기에서 좀 더 자유로워질 수 있다는 것에 큰 관심이 생긴것 같다.

 

 

- 강의를 듣고 난 후

 

그리고 강의에서 나온 내용들을 보면서 추가적으로 다른 것들에 대해 더 관심이 생기게 되었다. 예를 들어 아까 위에서 언급했듯이 React는 SPA를 구현하는 JavaScript 라이브러리이다. 강좌에서는 class를 통해서 SPA를 구현했고 React의 마찬가지로 class를 사용해서 개별적은 컴포넌트를 만들었다. 최근 버전의 React는 class 없이 function을 통해서 컴포넌트를 생성한다. 그 이유는 JavaScript의 class는 자바나 C++ 등과 같은 클래스 기반 객체지향언어와는 약간의 차이가 있기 때문이다. JavaScript는 함수형 프로그래밍이 가능하다. 이 말은, 강좌에서는 SPA를 class를 사용하여 객체지향으로 구현했지만 함수형으로 구현도 가능하다. 강좌를 보면서 기존에 알고있었던 객체지향 프로그래밍 말고도 함수형 프로그래밍에도 관심이 생겼다.

 

또한 3~5강엔 웹앱을 만들고 라이브러리를 만들어보면서 어떤 식으로 프로그래밍하며 그에 대한 구조와 동작들에 대해 들었다면 6강엔 JavaScript에 관한 참조 사전 형식의 강의였다. 기본적인것부터 다소 생각이 필요한 부분도 있으며 npm이나 번들러, REPL 등의 개념적인 부분과 개발하는 것에 있어서 필요한 도구 등을 소개해주는 강의도 있다. 누구에게나 그렇지만 해당 강의를 보면서 기본이 중요하다는 생각을 또 하게되었다. 본 강의에서 코드에대한 내용과 다른 여러가지 참조할 만한 강의가 있지만 동작을 완벽하게 이해하고 새로운 기능을 추가하려면 어느정도의 언어에 대한 특성이나 기본적인 지식은 필요하다고 생각되었다.

 

강사님도 강의에서 말씀하시는게 쉽게 변하는 기술은 배움에대한 비용은 상대적으로 적고 쉽게 변하지 않는 기술을 익히는데 배움에대한 비용이 크다고 한다. 쉽게 변하지 않는 기술은 단기간에 알 수 없기 때문에 꾸준한 학습이 필요하다. 그래서 이번엔 표지에 도마뱀이 그려져 있는 900페이지가 넘는 책도 샀다... 무엇보다도 강의를 들으면서 자바스크립트에 관한 기본적인 지식이 중요하다고 생각했다. cs적인 지식이나 어떤 언어에 대해서 공부를 할 때, 아무것도 모르는 상태에서 공부를 하는것과 어느정도 아는 상태에서 공부를 하는것과는 큰 차이가 있다고 생각한다. 아무것도 모를 때보다 어느정도 알게됐을 때 잘 기억되고 그것이 앞으로 다른 프로그램 등을 개발 할때 사용할 수 있는 나의 지식이 된다. 이 강의는 전부 다 알려준다고 할 수는 없지만 앞으로의 다른 지식들을 배우는데 있어서 도움이 될것 같다.

 

 

- 만들어본 결과물

 

개인적으로 처음으로 ES6 class와 TypeScript를 사용해서 만들어봤다. 물론 동작은 SPA로 동작한다. 강의를 처음 들으려고했던 이유처럼 리액트가 SPA인건 알겠는데 세세한 동작이라던지 어떤 식으로 구조를 가지고 있는지에 대해 좀 더 자세하게 알게되면서 라이브러리나 프레임워크 등은 사용하지 않고 순수 JavaScript & TypeScript로 만들었다. 또한 강의에서 언급되었던 webpack이나 node.js 등을 번들러나 서버로서 동작할 런타임 환경으로 사용했고 강의에는 없었지만 추가적인 기능으로 데이터관리를 위한 데이터베이스 연동이나 서버와 클라이언트의 중계기 역할을 하는 proxy 등을 사용했다.

 

첫 번째 사진은 강의에서 했던 방식대로 class를 만들고 공통적인 부분은 extends로 상속하면서 template을 따로 만들고 만들어지는 데이터에 따라 변경되는 부분만 렌더링해주는 방식으로 SPA를 구현한다. 폴더 구조도 그렇듯이 기능을 담당하는 파일 따로 html, css를 갖는 template따로 구분한다. 강의랑 한가지 다른 점은 여기선 css 파일도 각 해당하는 기능의 파일에 따라 독립적으로 작용한다. 동일한 css 이름이 사용되더라도 다른 컴포넌트의 css에 영향을 주지 않는다. css module 방식을 사용했다.

 

두 번째 사진은 서버를 node.js를 사용해서 구현했다. 강의를 진행하면서 JavaScript로 UI적인 부분 뿐만아니라 서버, 더 나아가서 추후에 앱이나 머신러닝도 할수 있기 때문에 해당하는 서버를 JavaScript 런타임인 node.js로 구현했다. express.js를 사용했고 데이터베이스와 연동하는 것, 로그인 처리, 데이터 API 받아오기 등의 기능을 구현한다. 현재는 데이터베이스로 mysql에 연동했고 화면의 전환에따라 데이터 API를 받아오는 정도의 기능이 구현되어 있다.

 

세 번째 사진은 webpack 번들러를 사용한 것이다. 강의에서는 parcel을 통해서 번들링을 한다. 강의의 특성상 parcel을 사용하게되면 webpack보다 설정하는 부분에 있어서 빠르게 설정할 수 있는 이점이 있다. 하지만 webpack 필수적이라고 할 정도로 많은 어플리케이션에 사용되며 더 상세한 설정을 직접 할 수 있기 때문에 결과물에서는 webpack을 사용했다. html, css, JavaScript, TypeScript 등의 설정 등을 여기서 할 수 있다. 번들링 한 파일의 위치나 이름, css모듈화 네이밍 형식, 파일 크기, 프록시 설정 등을 할 수 있다. 

 

네 번째 사진은 작업하던 결과물이다. SPA형식으로 작업했으며 하얀색 텍스트로 나열 되어있는 데이터는 공공데이터 포털에서 가져온 데이터이다. 아직 진행중이지만 강의에서 듣고 알게된 내용들을 적용해야 할 부분이 많다. 지금은 3-4강의 기본적인 구조에 맞춰서 화면의 라우팅과 렌더링 등을 통한 SPA의 기능 구현 정도이다.

 

 

- 결론

 

결론적으로 나에게 있어 여러모로 변화를 준 강의라고 생각한다. 회사에서도 개발을 꾸준히 해왔지만 일정 수준이나 시기에 도달했을 때, 비슷하고 반복적인 결과물이나 비슷한 코드들이 대부분이었다. 어찌됐건 내가 찾아보지 않으면 알수 없었던 부분이었다. 다른 곳으로 면접을 보러 다닐 때 SPA에 관해서 질문을 받은 적이 있다. 그때는 그냥 SPA라는 말 그대로 Single Page Application이라고 대답은 했지만 그것이 어떻게 동작하고 어떤 구조를 가지고 있는지는 몰랐다. 막연히 Multi Page Application 이랑 반대겠거니 생각만 했다가 그것에 대해 관심을 가지게 됐고 강의도 듣게 되었다. 처음 강의를 듣게 된 이유는 이것이 아니지만 결국엔 내가 궁금하고 관심있었던 부분까지 알게된 강의였다. 이것 뿐만 아니라 자바스크립트에 관심이 있거나 구조 등에 관해 궁금하다면 많은 도움이 될것같다. 나는 이 강의를 듣고 알게 된것도 많지만 앞으로 무엇을 공부해야 할지 어느정도 방향도 잡을 수 있다고 생각한다. 

 

 

 

https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #김민태의프론트엔드아카데미:제1강JavaScript&TypeScriptEssential

차트 라이브러리 만들어보기 - 하위 호환성 유지하기

 

소프트웨어를 만들고 버전을 관리 할때, 하위 호환성이란 것은 1.1.0이라는 버전이 나오고 해당 소프트웨어가 1.1.2로 올라갔을 때 올라간 버전의 소프트웨어가 하위 버전의 소프트웨어를 동작 시킬 수 있는가를 볼 수 있어야 한다. 물론 좋은 소프트웨어는 상위 버전이 하위 버전의 소프트웨어를 동작시킬 수 있어야 한다. 사용자 측에서 아무런 변경 없이 동작 시킬 수 있는것이 좋은 소프트웨어이지만 사실 이것은 매번 그럴 순 없다.

 

안되는 경우는 호환성을 깨야한다. 하지만 이 작업에서도 최대한 호환성을 깨지 않고 유지하려는 노력과 마인드가 필요하다. 이것은 라이브러리 뿐만 아니라 코드 자체에서도 적용된다. 어떤 코드를 만들던간에 이런 부분에 있는 사고와 고려는 필요하다.

 

호환성을 깬 부분에서 개발자는 새로운 인터페이스를 만들어야 할 것이고 사용자들에게 새롭게 만들어진 인터페이스를 사용하는 것으로 권장할 것이다. 하지만 새로운 인터페이스를 만들었다고해서 지금까지 기존의 사용자가 사용하는 이전 버전의 소프트웨어도 마찬가지로 계속 사용할 것이다. 그렇다면, 만약 이전 버전의 소프트웨어를 사용하는 사용자와 새로운 버전을 사용하는 사용자가 동시에 생길수 밖에 없다. 마이너한 부분을 수정했다면 이전 버전을 사용하는 사용자에게 메세지나 경고 표시를 통해 해당하는 기능을 사용하기 위해서 버전을 옮기는 것을 권고하는 것도 좋은 방법이다. 그리고 메이저 버전의 변경이 필요하다면  그에 따른 상세한 마이그레이션 가이드를 제공해야 한다. 관련하여 버전을 옮겨주는 소프트웨어까지 있다면 좋겠지만 그럴 수 없다면 가이드 내용을 상세히 할 필요가 있다. 

 

이러한 정보들을 통해서 꼭 소프트웨어나 라이브러리를 개발하지 않고 사용하는 입장이라 하더라도 사용자로서 이 소프트웨어나 라이브러리가 나에게 얼마나 적합하고 오랫동안 유지하면서 사용할 수 있는지에 대한 평가에 도움이 될 수 있다. 사용하려는 소프트웨어의 방향성이나 잘 관리되고 있는가에 대한 관점으로 보고 판단할 수 있을 것이다.

 

 

 

 

https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #김민태의프론트엔드아카데미:제1강JavaScript&TypeScriptEssential

차트 라이브러리 만들어보기 - 배포 및 버전 전략

 

이번 파트에서는 만들어 놓은 라이브러리를 배포하고 그에 관한 버전 등을 관리하는 방법에 대해 다룬다. 배포를 하는 방식으로는 이전엔 웹 사이트에 배포하려고 하는 라이브러리 등을 업로드하고 사용자들이 다운로드 하는 방식을 많이 사용하는 등 링크 자체를 통하는 방식이다. 예전의 방식이긴 하지만 아직도 이런 방식으로 배포와 사용을 하는 사이트들이 존재한다.

 

하지만 현재는 그런 다른 방식을 사용한다. 우리가 자바스크립트(js)를 사용하기 때문에 npm을 사용할 것이다. npm(node package manager)은 만들어놓은 라이브러리나 패키지를 등록하고 사용할 수 있으며 버전도 존재한다.

 

이런 npm을 사용하기 위해선 2가지의 고려해야할 사항이있다. 첫번째로는 공개의 여부이다. 내가 올리려고 하는 라이브러리가 외부적으로 공개해도 되는 내용인지 외부에 노출하지 않고 내부적으로만 사용해야 하는 것인지의 검토할 필요가 있다. 두번째는 라이센스의 문제이다. 어떤 라이센스의 원칙에 따라 배포할 것인지도 정해야한다. 라이센스로는 BSD나 MIT 등이 있는데 어떤 라이센스를 사용할 것인지도 검토해야한다. 대부분 공개적으로 오픈소스로 사용한다거나 특별한 제약을 두지 않는다면 MIT를 라이센스로 정한다. 

 

npm 사이트에 들어가면 관련하여 npm을 등록하거나 다운받는 것에 자세한 상세가 나와있다.

https://docs.npmjs.com/organizations/creating-and-managing-organizations

 

Creating and managing organizations | npm Docs

Documentation for the npm registry, website, and command-line interface

docs.npmjs.com

 

npm을 통해서 라이브러리를 올려놓고 관리하면서 git이랑 연동하는 것도 가능하다. 하지만 이 경우에는 버전관리가 힘들 수 있다. npm은 버전관리를 지원하지만 git 저장소를 직접 연결하는 경우엔 npm 패키지명, git 저장소 명의 형식으로 되어있기 때문에 버전관리가 어렵다. 하지만 소스 코드 자체를 공유하고 git을 사용한다면 git서브모듈을 통해서 필요하면 언제든지 다운받는 형식으로 사용할 수 있다.

 

 

 

 

https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #김민태의프론트엔드아카데미:제1강JavaScript&TypeScriptEssential

차트 라이브러리 만들어보기 - 차트 라이브러리 구현

 

index.js 파일에서 인스턴스로 만들어서 사용하는 chart.js 부분

import template from './chart.template';

const defaultOptions = {
  percent: 0,
  duration: 1000,
  frame: 30,
}

/**
 * Chart 클래스
 */
class Chart {
  #template = template;
  #el;
  #percent;
  #duration;
  #label;
  #frame;
  #handle;

  /**
   * 
   * @param {string} container - 마운트될 DOM 컨테이너 셀렉터
   * @param {string} data - 옵션 데이타 duration, frame
   * @example
   * new Chart('#root', {
   *   duration: 2000,
   *   frame: 20,
   * });
   */
  constructor(container, data = {}) {
    const { duration, frame, percent } = {...defaultOptions, data};

    this.#duration = duration;
    this.#frame = frame;
    this.#percent = percent;

    this.#el = document.querySelector(container);
  }

  /**
   * 퍼센트를 설정합니다.
   */
  set percent(per) {
    this.#percent = per;
  }

  /**
   * 애니메이션 시간을 ms 단위로 설정합니다.
   */
  set duration(dur) {
    this.#duration = dur;
  }

  /**
   * 에니메이션이 실행 될 때 초당 프레임을 설정합니다.
   */
  set frame(fr) {
    this.#frame = fr;
  }

  set label(text) {
    this.#label = text;
  }
  /**
   * 소스 데이타를 설정합니다.
   * @param {Array} source - 2차원 배열로 제공합니다.
   */
  setDataSource(source) {

  }
  /**
   * UI 업데이트를 수행합니다.
   */
  render() {
    this.#el.innerHTML = this.#template({
      percent: this.#percent * 10,
      duration: `${this.#duration / 1000}s`,
      label: this.#label,
    });

    const maxLoop = Math.floor(this.#duration / (1000/this.#frame));
    let loopCount = 0;

    this.#handle = setInterval(() => {
      loopCount++;

      this.#el.querySelector('#progress').innerHTML 
        = loopCount > maxLoop ? `${this.#percent}%` : `${Math.floor(this.#percent / maxLoop) * loopCount}%`;

      if (loopCount > maxLoop) {
        clearInterval(this.#handle);
      }
    }, 1000/this.#frame);
  }
}
 
export default Chart;

화면은 기존 방식과 동일하게 chart.template.js를 템플릿으로 가지고 import해서 사용한다. defaultOprtion 값은 아무것도 설정하지 않았을 때 가질 값을 정의한다.

 

Chart 클래스를 보면 #이 포함된 변수는 private 형태로 외부의 접근을 제한하고 클래스 내부에서만 사용할 수 있다. 차트 라이브러리에서 설정 되는 값으로는 duration, frame, percent 등으로 차트의 각각의 속성 값들을 정의한다. 각각의 프로퍼티에 접근제한을 걸어두었기 때문에 setter와 getter를 통해 값을 반환하고 수정한다. 그리고 여느 다른 클래스 구조와 같이 render 메소드를 통해서 화면을 렌더링한다. render 메소드 안에 el(Element를 지정)태그를 통해서 템플릿을 만들고 차트가 동적으로 생성될 것이므로 지정해 놓은 duration와 frame을 기반해서 setInterval() 기능을 사용한다. setInterval()은 몇 초마다 실행되는 기능이므로 clearInterval() 함수로 꼭 끝나는 지점에 사용해야 한다. 그렇지 않으면 메모리의 낭비가 발생한다. 

 

 

 

 

https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #김민태의프론트엔드아카데미:제1강JavaScript&TypeScriptEssential

+ Recent posts