개발공부/JavaScript

23_09_30 JS공부정리

coding-jake 2023. 9. 30. 18:11

https://www.youtube.com/watch?v=MlANtfGIlzI&list=PLcqDmjxt30RvEEN6eUCcSrrH-hKjCT4wt&index=48 

[제로초강의] 5-1~7-4
 
-window.crypto.getRandomValues() : 랜덤한 숫자를 뽑아내는 함수. Math.random()은 보안상의 위험이 있고 완전한 랜덤이 아니기 때문에 이 함수를 사용하는 경우가 있음
-new Set(배열)을 이용하여 배열속에 중복을 제거할 수 있음
-new Set(배열).size : 중복이 제거된 배열의 길이를 알 수 있음(요소의 종류)
-alert()는 undefined이므로 if의 조건문 안에 들어가면 false가 된다.
 
-배열.join('구분할문자열') : 배열을 문자열로 바꾸되, 구분할 문자열을 사이에 집어넣는다(없을경우 비우면됨) 
-문자열.split('구분할문자열') : 문자열을 배열로 바꾸되, 구분할문자열을 기준으로 요소를 쪼개서 배열로 바꿈(''를 넣을시 한글자씩 쪼갬)
-'\n' : 자바스크립트에서의 문자열 줄바꿈코드
-아니면 innerHTML에 <br/>코드를 넣어주면 줄바꿈이 됨
 
-appendChild는 요소를 CreatetextNode를 한 후에 해야하는데, append는 바로가능

    const message = document.createTextNode(`패배! 정답은 ${answer.join('')}`);
    $logs.appendChild(message);
    $logs.append(`${value} : ${strike}스트라이크 ${ball}볼`, document.createElement('br')); //콤마를 이용해 여러개 한꺼번에 넣기 가능.

 
-숫자야구 순서도

-숫자야구 코드(removeEventListener, 몇아웃 포함)

<html>
<head>
  <meta charset="utf-8">
  <title>숫자야구</title>
</head>
<body>
<form id="form">
  <input type="text" id="input">
  <button>확인</button>
</form>
<div id="logs"></div>
<script>
$input = document.querySelector('#input')
$form = document.querySelector('#form')
$logs = document.querySelector('#logs')

const numbers = [];
for (let n=0; n<9; n += 1) {
  numbers.push(n+1);
}

const answer = [];
for (let n=0; n<4; n+=1) {
  const index = Math.floor(Math.random()*(numbers.length));
  answer.push(numbers[index]);
  numbers.splice(index, 1)
}

const tries = [];
let outCount = 0;
function checkInput(input) {
  if (input.length !== 4) {
    return alert('4자리 숫자를 입력해주세요.');
  }
  if (new Set(input).size !== 4) {
    return alert('중복되지 않게 입력해주세요.');
  }
  if (tries.includes(input)) {
    return alert('이미 시도한 값입니다.');
  }
  return true;
  }

const onSubmitEvent = (event) => {
  if (tries.length === 10 || outCount === 3) {
    removeEventListener("submit", onSubmitEvent);
    return;
  }
  event.preventDefault();
  const value = $input.value;
  $input.value = '';
  if (!checkInput(value)) {
    return;
  }
  if (answer.join('') === value) {
    $logs.append(`홈런! 정답은 ${answer.join('')}!`)
    return
  }
  if (tries.length >= 9) {
    const message = document.createTextNode(`패배! 정답은 ${answer.join('')}`);
    $logs.appendChild(message);
    tries.push(value);
    return;
  }
  let strike = 0;
  let ball = 0;
  for (let n=0; n < answer.length; n += 1) {
    const index = value.indexOf(answer[n])
    if (index > -1){
      if (index === n){
        strike += 1;
      } else {
        ball += 1;
      }
    }
  }
 
  if ((strike === 0) && (ball === 0)){
    outCount += 1;
    $logs.append(`${value} : ${outCount}아웃!`, document.createElement('br'));
  } else {
    $logs.append(`${value} : ${strike}스트라이크 ${ball}볼`, document.createElement('br'));
  }
  if (outCount === 3) {
    $logs.append(`3아웃으로 게임패배! 정답은 ${answer.join('')}`)
    return;
  }
  tries.push(value);
}
$form.addEventListener("submit", onSubmitEvent)
</script>

-배열.forEach((element, index) => {함수}) : 배열의 element와 index를 파라미터로 받아서 배열이 끝날때까지 각각의 요소에 함수를 반복적용함
-아래 두코드는 동일하게 작동한다

일반 for문
forEach문

-배열.map((element,index) => {함수}) : 배열을 쉽게 수정가능
 

일반 for문
map문

-Array(숫자) : 숫자길이 만큼의 빈배열을 만드는 함수
-Array(숫자).fill(값) : 값을 빈 array에 다 채움(값을 생략하면 undefined가 됨)

Array(9).fill(0)

-map을 사용하는 이유. 편리하게 배열을 만들수 있음.

return값이 각각의 요소자리에 들어감

-피셔에이츠 셔플 : 배열 전체를을 무작위로 섞는것

    const candidate = Array(45).fill().map((e, i)=> {return i+1});
//피셔에이츠 셔플
    const shuffle = [];
    while (candidate.length > 0) {
      const random = Math.floor(Math.random * candidate.length);
      const spliceArray = candidate.splice(random, 1);
      const shuffledvalue = spliceArray[0];
      shuffle.push(shuffledvalue);
    }

-배열.slice(0,3) : 첫번째(0)부터 네번째 전까지(3)를 잘라낸후 반환. 원래 배열은 바뀌지 않음
*배열.slice(4, -1) : index4부터 index-1(뒤에서 첫번째 전) 까지
*배열.slice(4) : index4부터 끝까지
*배열.slice(-5, -1) : index-5(뒤에서 5번째)부터 index-1(뒤에서 첫번째)전까지
*배열.slice() : 원본을 복사함 (.sort와 같이 원본 배열을 변경하는 함수를 사용하고 원본을 유지하기 위해 사용)
*배열.slice().sort(() => {}) : 원본배열은 유지된채로 정렬된 배열을 새로 만들 수 있음
 
-array.sort(() => {}) : 배열을 정렬하는 함수. 원본을 수정함
*array.sort((a,b) => {return a-b}) : 오름차순 정렬
*array.sort((a,b) => b-a) : 내림차순 정렬
*arr.slice.sort((a,b => a.localeCompare(b)) : 문자열 오름차순
*arr.slice.sort((a,b => b.localeCompare(a)) : 문자열 내림차순
 
-setTimeout(함수, 밀리초) : 밀리초이후에 함수실행
 
-var는 함수스코프, let과 const는 블록스코프이다.
var는 함수이외에 다른 if문밖에서 접근이 가능(함수스코프)하다.
하지만 let,const는 함수,if문 등 블록의 밖에서는 접근이 안된다(블록스코프)
 
-함수스코프와 클로저 문제

    const winBalls = shuffle.slice(0, 6).sort((a,b)=>a-b);
   
    const showBalls = (number, $target) => {
      const $ball = document.createElement('div');
      $ball.classList.add('ball');
      $ball.textContent = number;
      $target.appendChild($ball);
    }
 
    for (var i=0; i<winBalls.length; i++){
      setTimeout(() => {
        showBalls(winBalls[i], $result);
    }, (i+1)*1000);
    }

*for문에 var로 변수를 지정하면 winBalls[i],i가 undefined,6로 출력되면서 제대로 동작하지 않게됨.
이는 for문이 동기적 실행, setTimeout이 비동기적 실행을 해서 그렇다.
var는 함수스코프이기때문에 전역변수로 다뤄져 for문이 6번동기적실행을 할때 이미 i가 6이되버린다.
그리고 시간이 지난후 setTimeout이 작동 하면서 비동기적으로 6번을 반복해서 실행하는데, 이때 이미 i가 6이므로 winBalls[6]은 undefined를 출력하고, i는 6이된다. 따라서 프로그램이 망가짐.
*var는 웬만하면 쓰지말고, 오래된 코드를 볼때 동작원리를 파악하기 위해서만 알아두는 지식이다.
 

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

그러면 let은 왜 잘실행되는가? let은 블록스코프이기떄문에 i값이 for문을 돌면서 고정되기 때문에 오류가 나지 않음
let으로 설정햇기때문에 i값이 밖으로 나가지 못하고 안에서 고정되어 반복문을 돌면서 i의 값이 순차적으로 고정됨
 
-var를 이용해서 정상적으로 실행하고 싶다면 다음과같이 작성하면 됨
(클로저문제-비동기함수와 함수스코프var가 만나면 생기는 문제)

클로저문제

- 전달된 i가 j로 전달되면서 j는 함수안에 갇히게되고(함수스코프니까)  j가 순차적으로 증가하면서 함수가 정상적으로 작동됨.
 
-다음과 같이 background에 url을 통해 이미지를 넣을 수 있고, 0 0을 조정하여 background에 들어가는 이미지의 위치를 조정할 수 있음
-backgroundSize를 통해 이미지의 크기를 지정할 수 있음

const $computer = document.querySelector('#computer');
const IMG_URL = './rsp.png'
$computer.style.background = `url(${IMG_URL}) 0 0`;
$computer.style.backgroundSize = 'auto 200px';

-background의 값을 변경할때는 backgroundSize도 항상 같이 적어줘야함

let coord = '0';
setInterval(()=>{
    if (coord === '0') { 
        $computer.style.backgroundPositionX = '0'
        $computer.style.backgroundSize = 'auto 200px';
        coord = '-220px';
    } else if (coord === '-220px'){
        $computer.style.backgroundPositionX = '-220px'
        $computer.style.backgroundSize = 'auto 200px';
        coord = '-440px';
    } else {
        $computer.style.backgroundPositionX = '-440px'
        $computer.style.backgroundSize = 'auto 200px';
        coord = '0';
    }    
}, 50);

-객체리터럴 A[]와 A.~~의 사용법
*A[]는 []안에 변수를 넣을 수 있음. A[변수]로 사용.
*A["문자열"] = A.문자열과 동일하며 A안의 문자열 키를 찾아 값을 반환함

const rspX = {
    scissors : '0',
    rock : '-220px',
    papper : '-440px',
}
 
let computerChoice = 'scissors';

-위의 경우 rspX.scissors = rspX["scissors"]와 같음.
-하지만 rspX.computerChoice를 하면 문자열 computerChoice가 객체리터럴 안의 키값으로 존재하지 않으므로 undefined를 반환함.
 -변수를 이용해 객체리터럴의 값을 반환하기 위해서는 rspX[computerChoice]라고 입력해야함.
 
-clearTimeout(setTimeout을 담은 변수) : timeout이 실행되기 전에 timeout을 멈출 수 있음.
-clearInterval(setInterval을 담은 변수) : interval을 멈출수 있음
 
-가위바위보에서 버튼을 연속클릭함으로써 손이 빨라지고 버튼을 눌러도 안멈추는 버그

let intervalId = setInterval(changeComputerHand, 50); //clearInterval을 하기 위해 setInterval의 반환값을 받는 코드
const clickButton = () => { //버튼을 눌렀을때 잠시 멈췄다가 다시 실행하는 코드
    clearInterval(intervalId);
    setTimeout(() => {
        intervalId = setInterval(changeComputerHand, 50) //setInterval반환값이 그때그때 달라지기 때문에 다시 넣어주는것
    }, 1000);
}

$rock.addEventListener("click", clickButton);
$scissors.addEventListener("click", clickButton);
$papper.addEventListener("click", clickButton);

원인 : 2번 연속으로 클릭했을때, 1번째 클릭하면 원래돌아가던 인터벌은 멈추고 1초이후에 1번인터벌이 시작됨. 
하지만 1초전에 한번더 버튼을 클릭하면
1번째 클릭한 인터벌이 시작하기도 전에 clearInterval을 함으로써 
1번인터벌이 안지워진채로 2번인터벌이 새로등록되고 1번인터벌의 intervalId값이 지워지고 2번인터벌의 id값이 저장됨.
그래서 1번인터벌과 2번인터벌이 동시에 돌아가면서 손이 아주 빠르게 작동하는것.
이 경우에 2번인터벌이후 3번인터벌을 등록하더라도 2번인터벌은 지워져도
1번인터벌은 계속 돌아가므로 버튼을 눌러도 손이 안멈추는것.
 
해결책1 : settimeout이 실생되기 직전에 다시 인터벌을 지운다

const clickButton = () =>
    clearInterval(intervalId);
    setTimeout(() => {
        clearInterval(intervalId); //여러번누르더라도 interval이 새로등록되기 직전에 직전의 인터벌을 지움으로써 하나의 인터벌만 실행되게 만듬.
        intervalId = setInterval(changeComputerHand, 50)
    }, 1000);
}

해결책2 : removeEventListener를 이용해서 버튼을 클릭했을때 이벤트를 지우고, settimeout을 이용해 시간이 지난후 이벤트를 다시 추가한다

const clickButton = () =>
    clearInterval(intervalId);
    $rock.removeEventListener("click", clickButton);
    $scissors.removeEventListener("click", clickButton);
    $papper.removeEventListener("click", clickButton); //이벤트 리스너를 제거하여 버튼을 못누르게 함
    setTimeout(() => {
        intervalId = setInterval(changeComputerHand, 50)  
        $rock.addEventListener("click", clickButton);
        $scissors.addEventListener("click", clickButton);
        $papper.addEventListener("click", clickButton); //이벤트 리스너를 추가하여 1초뒤에 다시 버튼을 누를수 있게 함
    }, 1000);
}

해결책3: clickable(플래그변수)를 이용해서 if문을 이용해 해결

let clickable = true; //플래그 변수 선언
const clickButton = () =>
    if (clickalbe){ //처음엔 무조건 허용
        clearInterval(intervalId);
        clickable =false;  //1초가 지나기 전까지 버튼을 눌러도 if문에 걸려서 코드가 실행되지 않게 만듬
        setTimeout(() => {
            clickable = true;  //1초가 지난 후에 다시 버튼을 눌르면 if문에 걸리지 않게 만듬
            intervalId = setInterval(changeComputerHand, 50
        }, 1000);
    }
}

-removeEventListener 주의점 / 고차함수인 경우 함수끼리 비교하는것이기 때문에 removeEvenetListener가 작동하지 않을 수 있음

-객체와 객체는 같지 않음. 안의 내용이 같더라도 메모리에서 서로다른 공간을 차지하고 있기 때문에 비교를 하면 false가 나옴.
-함수도 객체이기 때문에 fun(1) === fun(1)이 false가 나오는것.
-이를 해결하기 위해서는 함수를 변수에 넣어서 해결해야함

함수를 변수안에 넣어주어야함.

태그.addEventListener('click', fun1)
태그.removeEventListener('click', fun1)을 하면 정상적으로 고차함수도 removeEventListener가 작동하게됨.(확인필요)