본문 바로가기

Javascript/Javascript(2022 version)

Javascript 복습(2) - 클로저, 로또 Refactoring

728x90
반응형

1) Javascript 복습

   1-1) 클로저

   1-2) 로또 Refactoring

 

 

 

 

1) Javascript 복습

1-1) 클로저

function main() {
  for (var i = 0; i < 5; i++) {
    setTimeout(() => {
      console.log(i);
    }, 1000 * i);
  }
}

main();

// 문제
// var 키워드는 "함수 레벨 스코프"이므로
// main 함수 바로 밑(for 문 바로 위)에 "var i;"로 선언이 되고,
// setTimeout()의 경우, 본인 스코프(for 문 내부) 내에서는 변수 i를 찾을 수 없기에
// for 문 위로 올라와 변수 i를 찾게 되는데
// 이때 변수 i는 이미 for 문을 다 돈 상태이기에 결과적으로 "i = 5"의 값을 가지고 있는 상태이다!
// 따라서 결국 콘솔에는 setTimeout()에서 지정한 시간 간격을 두고 5가 5번 찍히게 된다!!

 

위의 코드 문제를 해결하는 방법

  1. var를 let 키워드로 바꾸면 됨!!
  2. 즉시실행함수로 실행시키면 됨!!
// 1. var를 let 키워드로 바꾸면 됨!!
function main() {
  for (let i = 0; i < 5; i++) {
    setTimeout(() => {
      console.log(i);
    }, 1000 * i);
  }
}

main();

 

// 2. 즉시실행함수로 실행시키면 됨!!
// 즉시실행함수 형태
// 1) 즉시실행함수(arrow function 버전)
(() => {
  console.log("asdf");
})();

// 2) 즉시실행함수(함수 선언문 버전)
(function () {
  console.log("asdf");
})();


// 해결
function main() {
  for (var i = 0; i < 5; i++) {
    ((j) => {
      setTimeout(() => {
        console.log(j);
      }, 1000 * j);
    })(i);
  }
}

main();

 

실행 컨텍스트, 스코프, 이벤트 루프를 이해해야 "클로저"를 정확히 이해할 수 있다!!

 

// 클로저 : 특정 함수의 return 값이 함수(고차함수 형태)일 때 상위 함수의 변수를 하위 함수에서 그대로 사용할 수 있는 것을 말한다!!
// 클로저 예시
function a() {
  let txt = "hello world!!";

  return () => {
    console.log(txt);
  };
}

const point = a();
console.log(point);
/*
// point 변수를 콘솔에 출력할 시 a 함수를 실행시킨 결과(return 값)인 아래의 화살표 함수 그 자체가 찍힌다!
() => {
  console.log(txt);
};
*/
console.log(point()); // hello world!!

 

const area = document.querySelector('#areaID'); // 코드 작업을 위해 임의로 해당 id를 가진 element가 있다고 가정함!!

const clickHandler = (i) => {
  const num = i + 1;

  return (e) => {
    console.log(num);
  };
};

area.addEventListener("click", clickHandler());

 

function inner() {
  let count = 0;
  function outer() {
    count++;
    return count;
  }
  return outer;
}

const counter = inner(); // outer 함수 자체가 counter 변수에 들어감!!
counter();
counter();
counter();
const a = counter();
console.log(a); // result: 4


// Counter 함수를 함수 표현식으로 선언함!!
const Counter = (type) => {
  // 매개변수가 "type"이다!!
  let count = 0; // Counter 함수 안에서 "count"라는 변수를 선언함!
  const increment = () => ++count; // Counter 함수 안에 있는 const 변수를 증가시키는 increment 변수 선언
  const decrement = () => --count; // Counter 함수 안에 있는 const 변수를 감소시키는 decrement 변수 선언

  return type === "increment" ? increment : decrement;
  
  /*
  // 바로 위의 삼항 연산자 구문과 동일한 if 문
  if (type === "increment") {
    return increment;
  } else {
    return decrement;
  }
  */
};

const increment = Counter("increment"); // () => ++count
const decrement = Counter("decrement"); // () => --count

increment(); // 1
increment(); // 2
increment(); // 3
decrement(); // -1
decrement(); // -2

const result = increment(); // 4
console.log(result); // 4


// Counter 함수의 다른 예시
const Counter = (type) => {
  let count = 0;

  return [() => ++count, () => --count];
};

const [increment, decrement] = Counter();
/*
// 바로 위의 배열 구조분해할당 구문과 동일한 코드
const CounterFn = Counter(); // Array [() => {}, () => {}]
const incre = CounterFn[0];
const decre = CounterFn[1];
*/
increment(); // 1
increment(); // 2
increment(); // 3
decrement(); // 2
decrement(); // 1

const result = increment(); // 2
console.log(result); // 2


// 바로 위의 Counter 함수의 return 값을 객체로 표현한 경우
const Counter = (type) => {
  let count = 0;

  return {
    increment: () => ++count,
    decrement: () => --count,
  }
};

const {increment, decrement} = Counter();
/*
// 바로 위의 객체 구조분해할당 구문과 동일한 코드
const CounterFn = Counter(); // Object {increment: () => {}, decrement: () => {}}
const incre = CounterFn['increment'];
const decre = CounterFn['decrement'];
*/
increment(); // 1
increment(); // 2
increment(); // 3
decrement(); // 2
decrement(); // 1

const result = increment(); // 2
console.log(result); // 2

함수 선언 시 만일 매개변수가 3개 이상이라면 객체로 받는 것이 좋다!!

일반함수를 사용하려는 경우에는 function 키워드 대신 arrow function을 쓰는 것이 좋다!!

const a = 10;
const add10 = (a) => a + 10;
console.log(add10(a)); // 20

 

// 고차함수는 아래와 같이 arrow function으로 표현하기 좋다!!
const f1 = () => () => 1;

// 예시 코드
const f2 = () => {
  let count = 0;
  return [() => ++count, () => --count];
};

// 바로 위의 예시 코드를 arrow function을 사용하여 변환한 코드
const f2 = (count) => [() => ++count, () => --count];

const Counter = f2(0);
console.log(Counter[0]()); // 1
console.log(Counter[0]()); // 2
console.log(Counter[1]()); // 1
console.log(Counter[0]()); // 2
console.log(Counter[0]()); // 3

// 바로 위의 arrow function 코드에서 count 변수의 초기값을 지정해준 코드 버전
const f2 = (count = 0) => [() => ++count, () => --count];

 

// (중요!!) Javascript 메커니즘 중 "함수는 값이다"라는 사실을 꼭 명심할 것!!

/*
function inner() {
  let count = 0;
  function outer() {
    count++;
    return count;
  }
  return outer;
}
*/

// 바로 위의 inner 함수를 arrow function을 사용해 변환한 코드
const make = (count) => () => ++count;

 

 

 

 

1-2) 로또 Refactoring

lotto.js(1 ~ 45개의 로또번호 중 랜덤으로 뽑은 6개의 숫자 배열과 해당 배열의 각 요소별 구간 나누기까지 구현)

const getNumber = (start, end) => {
  // if (end < start) return null;
  // return Math.floor(Math.random() * (end - start) + start) + 1;

  // 바로 위의 코드를 좀 더 간단히 refactoring한 코드
  const random = Math.floor(Math.random() * (end - start) + start);
  return end < start ? null : random;
};

const randomLotto = (length, count) => {
  // TODO : 1 ~ 45까지의 내용 만들기
  // TODO : 45개짜리 배열 생성 후 이 중 6개를 뽑아야 함!
  // TODO : Math.random 메서드를 활용해서 내용을 뽑기
  const result = new Array(count).fill(null);
  const lotto = new Array(length).fill(null).map((value, index) => index + 1);

  return result
    .map((v) => {
      // index를 뽑는 변수
      const lottoIndex = getNumber(1, lotto.length);
      // lotto 배열에서 lottoIndex의 인덱스 값의 요소를 뽑고, 배열에서도 제거
      const [number] = lotto.splice(lottoIndex, 1);
      return number;
    })
    .sort((a, b) => a - b);
};

const between = (number) => Math.ceil(number / 10) - 1;

const classPattern = {
  0: "a",
  1: "b",
  2: "c",
  3: "d",
  4: "e",
};

// for (const value in lotto) {
//   console.log(getClassName[between(lotto[value])]);
// }

const getClassName = (arr) => arr.map((v) => classPattern[between(v)]);
const lottoArray = randomLotto(45, 6);

console.log(lottoArray);
console.log(getClassName(lottoArray));

 

lotto.js(클로저를 활용한 Refactoring 버전)

const getLotto = ({ totalNumber, count }) => {
  const result = new Array(count).fill(null);
  const lotto = new Array(totalNumber)
    .fill(null)
    .map((value, index) => index + 1);

  const ClassPattern = {
    0: "a",
    1: "b",
    2: "c",
    3: "d",
    4: "e",
  };

  const getNumber = (start, end) => {
    const random = Math.floor(Math.random() * (end - start) + start);
    return end < start ? null : random;
  };

  const getSpliceNumber = (arr) => (v) => {
    const lottoIndex = getNumber(1, arr.length);
    const [number] = arr.splice(lottoIndex, 1);
    return number;
  };

  const randomLotto = () =>
    result.map(getSpliceNumber(lotto)).sort((a, b) => a - b);

  const between = (number) => Math.ceil(number / 10) - 1;
  const getClassName = (arr) => arr.map((v) => ClassPattern[between(v)]);

  const lottoArr = randomLotto();

  return {
    lotto: lottoArr,
    className: getClassName(lottoArr),
  };

  // console.log(lottoArray)
  // console.log(getClassName(lottoArray))
};

const config = {
  totalNumber: 45,
  count: 6,
};
const { className, lotto: lottoArr } = getLotto(config);
console.log(className, lottoArr);