TIL

0l0l·2021년 6월 11일
0

TIL

목록 보기
47/86

로또 추첨기(타이머 사용하기)

1. 공 뽑기(피셔 예이츠 셔플)

피셔-예이츠 셔플(Fisher-Yates Sguffle) : 일부가 아닌 전부 다 랜덤으로 섞는 알고리즘

<for문과 while문 사용하기 좋은 경우>

when?for문while문
조건복잡할 때간단할 때(i++ 없이 조건 자체가 알아서 변하는 상태)
실행(반복) 횟수정확히 알고있을 때(시작과 끝을 아는 상태)잘 모를 때(끝을 모르는 상태)
i++ or i-- 조건(변수값 증/감)있을 때없을 때

splice : 배열을 return하는 메서드

2. 뽑은 공 정렬하기(sort)

slice() : 배열 인덱스 구간을 잘라 새 배열에 복사하는 배열 메서드 (원본배열 영향 없음❌!!)
배열의 가장 앞 원소나 가장 뒷 원소를 뺄 때 유용하게 사용할 수 있다.

// array.slice(start(인덱스-포함o)[, end(인덱스-포함x)])

const array = [3, 6, 2, 8, 3, 2, 6, 7];
array.slice() // [3, 6, 2, 8, 3, 2, 6, 7] :'그대로 복제'
array.slice(0, 3) // [3, 6, 2]
array.slice(6) // [6, 7] :인덱스 6부터 끝까지

// 긴 배열에서 뒤에 위치한 원소의 경우, '음수 인덱스'를 활용!★
array.slice(-3, -1) // [2, 6] :인덱스 -3값, 인덱스 -2값
array.slice(3, -1) // [8, 3, 2, 6] :인덱스 3값 ~ 인덱스 -2값

splice() : 배열에 삭제할 원소 개수, 추가할 원소값을 받아 원본 배열객체를 수정(이어붙이는)하는 메서드, 삭제된 배열을 반환함 (원본배열 영향 있음⭕!!)

// array.splice(start(인덱스)[, 삭제할 원소 개수, [추가할 원소값..]])

const array = ['nice', 'day', 'sunny', 'coffee'];
console.log(array.splice(1, 2)); // ['nice', 'coffee'] :인덱스 1부터 원소 2개 삭제

const array = ['nice', 'day', 'sunny', 'coffee'];
const delArr = array.splice(1, 2, 'time');
console.log(array); // ['nice', 'time', 'coffee'] :인덱스 1부터 원소 2개 삭제, 삭제한 자리에 'time' 원소값 추가
console.log(delArr); // ['day', 'sunny'] :삭제된 배열 반환

<slice 메서드와 splice 메서드 차이>

차이점slicesplice
원본배열변하지 않음변함
새로운 원소값 추가불가(X)가능(O)
두번째 인자 자리끝 인덱스(<-삭제 제외)삭제할 원소 개수

원본 배열이 변하지 않는 메서드 map(), slice() 기억하기!
Tip! 다시 원본 배열을 사용할 수 있도록 최대한 원본 배열을 수정하지 않는 메서드를 이용해 코드 작성하기!


sort() 메서드는 원본 배열을 수정⭕함으로써 영향을 끼친다!!
return 값에 따라 배열이 오름차순 또는 내림차순으로 정렬할지 결정된다.

  • 0보다 작은 경우(a-b < 0): a를 b보다 낮은 인덱스로 정렬(오름차순)
  • 0을 반환(a-b = 0): a와 b를 변경하지 않음(순서 유지)
  • 0보다 큰 경우(a-b > 0): b를 a보다 낮은 인덱스로 정렬(내림차순)
array.sort((a, b) => {
  return a - b;
}); // 오름차순

array.sort((a, b) => {
  return b - a;
}); // 내림차순

Tip!😆 sort() 메서드와 같이 원본 배열을 수정하는 메서드를 사용하는 경우, 원본 배열이 수정되지 않도록 slice() 메서드를 이용한다!
array.slice()는 원본과 동일한 것이 아니라 복사한 새 배열이다.(참조x)

array.slice().sort((a, b) => a - b); // 오름차순
array.slice().sort((a, b) => b - a); // 내림차순

sort() 메서드는 배열 뿐만 아니라 'return -1/0/1' 관계에 따라 문자열도 정렬 가능하다.
문자열은 첫번째 단어의 아스키(ASCII) 코드 넘버를 기준으로 비교하여 정렬된다.

const arr = ['app', 'js', 'bite', 'number', 'code'];

arr.slice().sort((a, b) => a[0].charCodeAt() - b[0].charCodeAt());
// ['app', 'bite', 'code', 'js', 'number'] :오름차순
arr.slice().sort((a, b) => b[0].charCodeAt() - a[0].charCodeAt());
// ['number', 'js', 'code', 'bite', 'app'] :내림차순

첫번째 단어가 동일한 (English/한글)문자열 같은 경우, 다른 값인 두번째 단어를 기준으로 비교한다.

const arr = ['konyang', 'bbirong', 'young k', 'yd'];

arr.slice().sort((a, b) => a.localeCompare(b));
// ['bbirong', 'konyang', 'yd', 'young k'] :오름차순
arr.slice().sort((a, b) => b.localeCompare(a));
// ['young k', 'yd', 'konyang', 'bbirong'] :내림차순

3. 일정 시간 후 실행하기(setTimeout)

setTimeout의 첫번째 인자는 함수 자리이다!
두번째 자리의 단위인 밀리초이다. (1초 = 1000밀리초)

// 문법
setTimeout(() => {
  // 특정 시간 이후에 실행되는 함수 ;콜백함수
}, 밀리초);

setTimeout(() => { // 상동
  func();
}, 1000);
setTimeout(func, 1000); // 내부 함수가 하나이고, 인수가 없는 경우 작성 가능
❌setTimeout(func(), 1000);// ()를 붙이면 바로 실행되고, 인자에 함수가 아닌 return값이 들어가게 됨❗
setTimeout(() => {
  const $ball = document.createElement('div');
  $ball.className = 'ball';
  $ball.textContent = winBalls[0]; // <div class="ball">winBalls[0]</div>
  $result.appendChild($ball);
}, 1000);
// ...(중복되는 코드)
setTimeout(() => {
  const $ball = document.createElement('div');
  $ball.className = 'ball';
  $ball.textContent = winBalls[5];
  $result.appendChild($ball);
}, 6000);

1초씩 지날 때마다 뽑은 숫자를 한 개씩 화면에 표시한다.
위처럼 중복된 코드는 함수로 따로 만들어 호출하는 방식으로 작성한다.
중복 코드에서 변하는 부분을 매개변수로 설정한다.
(코드가 정상적으로 작동하는 전제 하에) 중복된 코드를 제거하는 'Refactoring' 작업을 통해 코드를 깔끔하게 만들 수 있다.

// 함수 선언(number: 화면에 표시할 번호, $parent: 출력할 요소)
const drawBall = (number, $parent) => {
  setTimeout(() => {
    const $ball = document.createElement('div');
    $ball.className = 'ball';
    $ball.textContent = number;
    $parent.appendChild($ball);
  }, );
};

/* --- 중복 제거1 --- */

// 함수 호출
setTimeout(() => {
  drawBall(winBalls[0], $result);
}, 1000);
setTimeout(() => {
  drawBall(winBalls[1], $result);
}, 2000);
// ...(3~6번째 숫자 호출)
setTimeout(() => {
  drawBall(bonus, $bonus);
}, 7000);

/* --- 중복 제거2 --- */
// 6자리 숫자 뽑기
for (let i = 0; i > winBalls.length; i++) {
  setTimeout(() => {
    drawBall(winBalls[i], $result);
  }, (i + 1) * 1000);
}

setTimeout(() => {
  drawBall(bonus, $bonus);
}, 7000);

for문 변수 i를 밀리초로 만들기 위한 수식 : (i + 1) * 1000
[0, 1, 2, 3, 4, 5] -> [1000, 2000, 3000, 4000, 5000, 6000]

🔆 배열의 map() 메서드를 이용해 배열의 요소들을 원하는 값으로 바꿀 수 있다. (map 함수를 이용해 반복문 만들기 연습!)

4. 블록/함수 스코프, 클로저 문제

비동기 코드: 실제로 코딩한 순서와 다르게 동작하는 코드 (ex. 이벤트 리스너, 타이머)

<var와 let의 차이>
변수는 스코프(변수에 접근 가능한 범위)라는 것을 가지며 var는 함수 스코프를 가지고, let은 블록 스코프를 가진다.

var

함수 스코프 : 함수를 경계로 접근 여부가 달라지는 것
함수 안에서 선언한 변수를 함수 밖에서 호출하면 에러 발생하고 접근이 불가하다.
함수가 아닌 일반 블록(if문, for문 등)을 경계로 할 때는 접근이 가능하다.

for (var i = 0; i < 5; i++) {}
  console.log(i); // 5
// i = 4일 때 i++가 실행되고, i = 5일때 조건이 false로 실행 종료★

let/const

블록 스코프 : 블록(중괄호{ })을 경계로 접근 여부가 달라지는 것
블록 안에서 선언한 변수를 블록 밖에서 호출하면 에러 발생하고 접근이 불가하다.
if문, for문, while문에 해당하며, var일 때와 let일 때 각각 차이가 있다.

for (let i = 0; i < 5; i++) {}
  console.log(i); // Uncaught ReferenceError: i is not defined

위치 상으로는 let이 블록 밖에 있지만 for문의 경우 블록 안에 있는 것으로 생각하여, for문 블록 바깥에서 변수 i를 접근할 때 에러가 발생한다.


<var일 때 예시>
반복문(for문)은 동기이기 때문에 매우 빠르게 실행되고, 반복문이 돌 때 실행되는 '(i + 1) * 1000'는 바로 'i = 6'이 된다.
setTimeout의 콜백 함수는 비동기이기 때문에 함수가 실행되는 시점에는 이미 'i = 6'인 상태로 타이머가 돌며 winBalls[6]은 undefined이 된다.

for (var i = 0; i < winBalls.length; i++) {
  setTimeout(() => {
    console.log(winBalls[i], i);
    drawBall(winBalls[i], $result);
  }, (i + 1) * 1000);
}

<let일 때 예시> (위의 코드에서 var -> let)
밖에서 안으로 접근하는 것을 막아주며, 안에 있는 것을 밖으로 나가지 못하게 막는다.
for문에 쓰이는 let은 하나의 블록마다 변수 i가 고정된다.(var일 때 변수 i는 고정되지 않아 일반적인 동작대로 실행)
setTimeout 콜백 함수 안에 i도 setTimeout을 호출할 때의 i와 동일하다.

var는 함수 스코프이기 때문에 고정❌, let은 블록 스코프이기 때문에 고정⭕
∴ var는 함수 유무, let은 블록 유무에 따라 달라진다!


클로저

클로저 : 함수와 함수 밖에 있는 변수와의 관계
<var를 사용할 때 스코프 관련 문제 해결 방법>
함수와 함수 밖에 있는 변수를 함수와 함수 안에 있는 변수의 형태로 바꿔준다.

var는 함수 스코프이고 함수에 영향을 미치기 때문에 위의 코드에서 함수를 생성해 함수 안에 코드를 넣는다.
✔변경 전: 함수 밖의 변수 i를 사용 / ✔변경 후: 함수 안의 변수 j를 사용

for (var i = 0; i < winBalls.length; i++) {
  (function(j) { // 괄호로 감싸고 인수를 추가해 바로 호출할 수 있도록 함
    setTimeout(() => {
      console.log(winBalls{j], j);
      drawBall(winBalls[j], $result);
    }, (i + 1) * 1000);
  })(i);
}

반복문과 var를 쓸 때 항상 스코프 문제가 생기는 것은 아니고, 함수 스코프를 가진 var비동기 함수(ex. setTimeout(), addEventListener())가 만나면 클로저 문제가 발생한다.

현재는 let을 사용하기 때문에 이러한 문제가 발생하지 않지만 이전에 사용한 코드들에서 발생되기 때문에 알아두기!😙

profile
천방지축 빙글빙글

0개의 댓글