1018 - 1024

1018

강남으로의 첫 등교. 새로운 시작

모듈이라는 개념은 전체가 즉시실행 함수로 감싸져 있다.

자바스크립트는 콜스택이 하나인 싱글 스레드 언어이다.
-> 자바스크립트 엔진이 싱글 스레드
이 말은 한 번에 하나의 일만 수행할 수 있다는 것을 의미한다.

예를 들어 10초 걸리는 일이 앞에 있고, 그 뒤에 함수가 있는 경우 뒤에 있는 함수가 블로킹 된다.
이를 해결하기 위해 비동기 처리를 해야하는데, 10초 걸리는 일을 브라우저에게 위임한다.
-> 브라우저는 멀티스레드
콜스택과 이벤트 루프, 태스크 큐 세 개가 협동하여 비동기 처리를 한다.

서버 처리를 할때는 에러가 발생할 확률이 높아서 에러처리를 해줘야 한다.

프로그래머스 Level2 - JadenCase 문자열 만들기

const solution = (s) =>
  s.replace(/[^ ]+/g, (s) => s[0].toUpperCase() + s.slice(1).toLowerCase());
  • 한 줄 코드로 문제를 해결했다.
  • 공백이 아닌 문자열이 계속되는 경우 해당 문자열을 맨 앞글자는 대문자로, 그 뒤 문자열은 소문자로 변환하였다.

1019

페어 프로그래밍을 진행하면서 상태를 저장해야 하는 경우가 있었는데, 쿠키와 로컬 스토리지 세션 스토리지 중에서 어떤 것을 사용해야 할 지 명확하게 판단을 내릴 수가 없었다. 이번 기회에 각 방식의 특징과 어떤 경우에 무엇을 사용할지 판단을 내리기 위해서 이번 글을 작성하게 되었다



쿠키

HTTP 쿠키는 서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각이다. 브라우저는 그 데이터 조각들을 저장해 놓았다가, 동일한 서버에 재요청 시 저장된 데이터를 함께 전송한다.

과거엔 클라이언트 측에 정보를 저장할 때 쿠키를 주로 사용하곤 했다. 쿠키를 사용하는 게 데이터를 클라이언트 측에 저장할 수 있는 유일한 방법이었을 때는 이 방법이 타당했지만, 지금은 웹 스토리지를 사용해 정보를 저장하는 것을 권장한다.


쿠키의 단점

  • http 통신을 할 때 매번 서버에 전송된다.
  • 로컬 스토리지는 5mb까지의 데이터를 저장할 수 있는 반면, 쿠키는 4kb까지의 데이터만 저장할 수 있다.



웹 스토리지

웹 스토리지 API는 브라우저에서 키/값 쌍을 쿠키보다 훨씬 직관적으로 저장할 수 있는 방법을 제공한다. 종류로는 로컬 스토리지세션 스토리지가 있다. 이들은 window 전역 객체의 프로퍼티로서 존재하며, 같은 Storage 객체를 상속받기 때문에 동일한 메소드들을 가진다.

쿠키와 달리 서버에 전송되지 않으므로 서버에 부담이 가지 않고, 더 많은 데이터를 저장할 수 있다.


로컬 스토리지와 세션 스토리지

로컬 스토리지는 세션 스토리지와 비슷하지만, 로컬 스토리지의 데이터는 만료되지 않고 세션 스토리지의 데이터는 페이지 세션이 끝날 때 제거되는 만료성의 차이가 있다.

  • 페이지 세션은 브라우저가 열려있는 한 새로고침과 페이지 복구를 거쳐도 남아있다.
  • 페이지를 새로운 탭이나 창에서 열면, 새로운 세션을 생성한다.
  • 탭/창을 닫으면 세션이 끝나고 세션 스토리지 안의 객체를 초기화시킨다.


웹 스토리지의 키와 값은 항상 각 문자에 2바이트를 할당하는 DOMString의 형태로 저장한다. 객체와 마찬가지로 정수 키는 자동으로 문자열로 변환한다. 불리언 타입으로 값을 저장해서 사용하고 싶다면, 값을 받아올 때 JSON.parse 메서드를 사용하면 된다. 또는 객체를 값으로 저장하고 싶다면 JSON.stringify 메서드를 사용하면 된다.



정리

로컬 스토리지와 세션 스토리지는 데이터의 세션이 종료되어도 데이터를 유지할 것이냐 하지 않을 것이냐에 따라 무엇을 사용할 지 결정한다.

웹 스토리지와 쿠키 중 무엇을 사용할지는 몇 가지 사항을 고려해야 한다.

우선 로컬 스토리지는 자바스크립트 코드를 통해 삭제하지 않으면 데이터는 자동으로 삭제되지 않기 때문에 큰 데이터를 더 오랜 시간동안 저장해야 하는 경우에 유용한다.

다만 로컬 스토리지를 잘 사용하려면 저장된 데이터의 위협 수준이 낮아야 한다. 쿠키는 HttpOnly를 사용하면 XSS를 방어할 수 있는 반면, 로컬 스토리지는 XSS에 취약할 수 있기 때문이다.

그렇기 때문에 프론트엔드 개발자 입장으로 생각해 봤을 때, 보안적인 측면에서 중요성이 높지 않은 데이터들은 만료일을 설정해줘야 하는 경우ex) 하루동안 이 팝업창 보지 않기에만 쿠키를 사용해 저장하고, 그 외에는 웹 스토리지 API를 사용해야겠다고 결론을 내렸다.

보안적인 측면에서 중요성이 높은 데이터의 경우에는 아직 결론을 내리지 못했지만 계속해서 고민해봐야겠다.

1020

프로그래머스 카카오 Level2 - 거리두기 확인하기

function solution(places) {
  const answer = [];
  const dx1 = [-1, 0, 1, 0];
  const dy1 = [0, 1, 0, -1];
  const dx2 = [-1, 1, 1, -1];
  const dy2 = [1, 1, -1, -1];
  const dx3 = [-2, 0, 2, 0];
  const dy3 = [0, 2, 0, -2];

  function isRight(place) {
    for (let i = 0; i < place.length; i++) {
      for (let j = 0; j < place[i].length; j++) {
        if (place[i][j] === "P") {
          for (let k = 0; k < 4; k++) {
            const nx = i + dx1[k];
            const ny = j + dy1[k];

            if (nx >= 0 && ny >= 0 && nx < 5 && ny < 5 && place[nx][ny] === "P")
              return false;
          }

          for (let k = 0; k < 4; k++) {
            const nx = i + dx2[k];
            const ny = j + dy2[k];

            if (
              nx >= 0 &&
              ny >= 0 &&
              nx < 5 &&
              ny < 5 &&
              place[nx][ny] === "P" &&
              (place[nx][j] !== "X" || place[i][ny] !== "X")
            )
              return false;
          }

          for (let k = 0; k < 4; k++) {
            const nx = i + dx3[k];
            const ny = j + dy3[k];

            if (
              nx >= 0 &&
              ny >= 0 &&
              nx < 5 &&
              ny < 5 &&
              place[nx][ny] === "P" &&
              place[(i + nx) / 2][(j + ny) / 2] !== "X"
            )
              return false;
          }
        }
      }
    }
    return true;
  }

  for (const x of places) {
    if (isRight(x)) answer.push(1);
    else answer.push(0);
  }

  return answer;
}
  • 좌표를 그리고 상황을 나눠서 문제를 해결하였다.
  • 맨해튼 거리를 기준으로 거리두기를 준수하지 않은 경우를 생각해보니 1. 현재 요소가 P일 때 상하좌우 +1의 거리에 P가 있는 경우 2. 현재 요소가 P고 대각선 중 한 곳에 P가 있는데, 대각선 사이에 둘 다 X가 아닐 때 3. 현재 요소가 P일 때 상하좌우 +2의 거리에 P가 있고 그 사이가 X가 아닐 때 이 세 경우가 존재했다.
  • 결과로 배열을 반환해야 했기에 반복문을 돌려서 각 대기실이 거리두기를 준수했는지 아닌지를 확인했다.
  • 인덱스를 잘못 계산하여 해결되지 않는 테스트케이스가 존재했는데, 이를 염두에 두고 계산을 잘 해야겠다고 생각했다.

프로그래머스 Level2 - 모음사전

function solution(word) {
  let answer = 0;
  const str = "AEIOU";
  const arr = [];
  let tmp = [];
  function DFS(L, n) {
    answer++;
    if (L === n) arr.push(tmp.slice(""));
    else {
      for (let i = 0; i < 5; i++) {
        tmp.push(str[i]);
        DFS(L + 1, n);
        tmp.pop();
      }
    }
  }

  for (let i = 1; i <= 5; i++) {
    tmp = [];
    DFS(0, i);
  }
  answer = arr.sort().findIndex((v) => v.join("") === word) + 1;

  return answer;
}

console.log(solution("AAAAE"));
  • 정말 간단한 문제라고 생각했는데 생각보다 시간이 조금 걸렸다.
  • DFS를 사용하여 수학 계산 중, 부분집합 구하기, 순열 구하기, 중복 순열 구하기, 조합 구하기를 사용할 줄 아는데, 이 중 무엇을 활용할지 헷갈렸다.
  • 사전 상으로 정렬하면 되므로 1개부터 5개까지 모든 중복 순열을 구한 뒤, 정렬하여 답을 찾았다.

1021

프로그래머스 카카오 Level3 - 보석 쇼핑

function solution(gems) {
  let answer = [];
  const sh = new Map();
  let lt = 0;

  for (const gem of gems) {
    sh.set(gem, (sh.get(gem) || 0) + 1);
  }
  const len = sh.size;
  sh.clear();

  for (let rt = 0; rt < gems.length; rt++) {
    sh.set(gems[rt], (sh.get(gems[rt]) || 0) + 1);

    while (sh.size === len) {
      sh.set(gems[lt], sh.get(gems[lt]) - 1);
      if (sh.get(gems[lt]) === 0) sh.delete(gems[lt]);
      answer.push([lt + 1, rt + 1]);
      lt++;
    }
  }

  answer.sort((a, b) =>
    a[1] - a[0] > b[1] - b[0]
      ? 1
      : a[1] - a[0] < b[1] - b[0]
      ? -1
      : a[0] > b[0]
      ? 1
      : -1
  );

  return answer[0];
}
  • 카카오 레벨 3단계 문제라 엄청 어려울 것이라고 생각했는데, 생각보다 금방 풀렸다.
  • 투포인터 알고리즘을 사용해서 보석 종류의 개수를 처음에 구하고, map으로 보석을 저장하여 모든 종류의 보석을 가지고 있을 때의 경우를 모두 구해 정렬하여 답을 찾았다.

프로그래머스 Level2 - 짝지어 제거하기

function solution(s) {
  let answer = 1;
  const nh = [];

  for (let x of s) {
    if (nh[nh.length - 1] === x) {
      nh.pop();
    } else {
      nh.push(x);
    }
  }

  answer = nh.length === 0 ? 1 : 0;

  return answer;
}
  • 다르게 말하면 스택인지 아닌지 찾는 문제인대, 이를 빠르게 알아차리는 것이 핵심인 문제였다.
  • 효율성이 들어가면 당연히 투포인터, 이분탐색 또는 DP까지 생각하게 되는데 이러한 고정관념을 내려놓을 필요도 있다고 느꼈다.

1022

3일간 미니 프로젝트를 하면 느낀 점

Canvas API

왜 캔버스를 사용했는지?
Canvas API는 JS와 <canvas> 엘리먼트를 통해 그래픽을 위한 수단을 제공하는데, 제공되는 메서드를 사용해서 배경 색을 그리거나 이미지를 지정하고, 원하는 텍스트를 삽입하여 다운로드 받기 위해서 캔버스 API를 사용하게 되었다. 캔버스의 크기와 높이, 어떤 폰트 크기와 글꼴로 텍스트를 입력할 지 등의 정보를 상태로 저장하고, 상태가 변경될 때마다 캔버스를 새롭게 그려줬다.

기존에 있던 무료 배너 생성기는 크기 조정이 숫자 값으로 직접 입력해서만 가능했던 데에 비해, 비율을 고정한 채 크기를 조정하는 기능도 있으면 좋겠다고 생각해서 input type range로 크기를 조정하는 기능을 추가했다. 이 기능을 구현할 때 처음에는 onchange라는 이벤트를 사용하면 될 것이라고 생각했다. 하지만 이를 사용하면 화면에 변화가 즉각적으로 발생하는 것이 아니라 마우스를 드래그하고 뗀 후에 이벤트가 발생하는 것을 알 수 있었다. 찾아보니 oninput이라는 이벤트가 있었는데 이는 제가 원하던 대로 값이 즉각적으로 변화되어 화면이 이에 맞게 변화되는 것을 확인할 수 있었다. 여기서 input 사용자가 콘트롤 패널을 통해 데이터를 바꾸는 순간 발동되고, change 이벤트는 value가 정해지면 발동되는 이벤트라는 것을 알게 되었다. 즉 input은 모든 상황에서 change 이벤트보다 먼저 발동된다.

이번에 제가 구현한 가장 재밌었던 파트는 캔버스의 텍스트 멀티 라인을 구현하는 파트였다. 처음에는 엔터를 치면 운영체제 환경에 맞게 라인피드나 캐리지 리턴 등의 이스케이프 문자들이 들어갈 것이라고 생각해서, 이를 <br> 태그로 바꿔서 화면에 출력하면 정상 출력되지 않을까 생각했다. 하지만 생각과는 다르게 화면에는 <br> 문자열이 그대로 출력되었고, 이유를 찾아보니 캔버스 API의 텍스트를 출력하는 메서드 동작방식이 텍스트와 캔버스 내부에서 텍스트를 표시할 x축 값과 y축 값을 계산하여 화면에 표시해줘야했다. 이때 아.. 이래서 기존 사이트에서 두 줄 이상의 텍스트 출력을 구현하지 않았구나 하는 생각이 들었다. 검색을 해봐도 결과도 거의 없고, 다른 사람의 코드를 가져다 쓰는 것은 적어도 배우는 기간동안은 하고 싶지 않아서 그냥 기존 사이트처럼 한 줄만 출력하게 할까 잠깐 고민을 했다. 하지만 오기가 생겨서 함수를 어떻게 구현할 지 노트에 설계했다. 캔버스의 높이와 텍스트를 라인피드나 캐리지 리턴으로 정규식 처리하여 배열을 구하고, 그 배열의 길이에 상태에 저장하고 있는 폰트사이즈를 곱하면 이 텍스트가 차지해야 하는 높이를 구할 수 있고 그렇다면 첫 번째 줄이 시작해야 하는 y 좌표를 구할 수 있지 않을까 생각했다. 그 결과 생각한대로 맞아떨어졌고 배열을 반복문을 돌려서 원하는 좌표 위치에 텍스트를 출력할 수 있었다. 이렇게 조금 엉성하지만 남의 코드를 참조하거나 베끼지 않고 저의 한계점을 돌파했다고 느낀 이 순간이 프로젝트 중 가장 즐거운 순간이었고, 이 경험을 통해 프로젝트를 시작하기 전보다 개발자로서의 마음가짐 측면에서 성장했다고 느꼈다.

하나의 파일로 공동 작업 시 문제점

다른 페이지로 이동 없이 하나의 페이지로만 구성된 애플리케이션을 개발하다 보니 어쩔 수 없이 3명의 팀원이 html, css, js 파일을 공유하여 한 파일에 각자 개발을 진행했다. 그러다보니 각자 기능을 개발하다가 합치는데 오히려 시간이 들어 배보다 배꼽이 큰 현상이 발생했다. 특정 이벤트가 발생할 때마다 하나의 캔버스에 접근하여 상태를 변경시키고 화면을 렌더링 했는데, 개발하다보니 각자 추가로 필요한 상태가 발생했고 이런 상황을 공유하고 해결하면서 프로젝트를 진행하기에 어려움을 겪었다.

편의를 위해서 처음부터 모듈화를 하지 않고 리팩토링 때 모듈화를 해야겠다고 미뤘는데, 소스 파일 내에서 상태를 두고 사용하다 보니 프로젝트가 어느 정도 진행 됐을 때 상태를 참조하거나 세팅하는 코드를 하나하나 찾아내고, 바꾸는 것이 여간 힘든 일이 아니었다. 이번 프로젝트를 통해서 설계의 중요성을 다시금 느꼈고 이를 만족스럽게 하지 못한 데에 아쉬움이 남는다. 또한 협업의 능률을 올리기 위해서는 페이지 별로 작업을 하는 것이 좋겠다고 생각했다.

1023

타이머

호출 스케줄링

  • 함수를 명시적으로 호출하지 않고 일정 시간이 경과된 이후에 호출되도록 함수 호출을 예약하려면 타이머 함수를 사용한다. 이를 호출 스케줄링이라 한다.
  • 타이머 함수는 ECMAScript 사양에 정의된 빌트인 함수가 아니다. 하지만 브라우저 환경과 Node.js 환경에서 모두 전역 객체의 메서드로서 타이머 함수를 제공한다. 즉, 타이머 함수는 호스트 객체다.
  • 타이머 함수는 모두 일정 시간이 경과된 이후 콜백 함수가 호출되도록 타이머를 생성한다.
  • JS 엔진은 단 하나의 실행컨텍스트 스택(콜 스택)을 갖기 때문에 두 가지 태스크를 동시에 실행할 수 없다. -> 싱글 스레드로 동작
  • 이런 이유로 타이머 함수 setTimeout과 setInterval은 비동기 처리 방식으로 동작한다.

타이머 함수

setTimeout / clearTimeout

setTimeout 함수는 두 번째 인수로 전달받은 시간(ms)으로 단 한 번 동작하는 타이머를 생성한다. 이후 타이머가 만료되면 첫 번째 인수로 전달받은 콜백 함수가 호출된다.
-> 즉, setTimeout 함수의 콜백 함수는 두 번째 인수로 전달받은 시간 이후 단 한 번 실행되도록 호출 스케줄링된다.

  • 콜백 함수 대신 코드를 문자열로 전달하면 타이머가 만료된 뒤 해석되고 실행되나 권장하지는 않는다.
  • 타이머 만료 시간을 생략할 경우 기본값 0이 지정된다.
  • delay 시간은 태스크 큐에 콜백 함수를 등록하는 시간을 지연할 뿐이다. 타이머가 만료되면 콜백 함수가 즉시 호출되는 것이 보장되지는 않는다.
  • delay가 4ms 이하인 경우 최소 지연 시간 4ms가 지정된다.
  • 호출 스케줄링된 콜백 함수에 전달해야 할 인수가 존재하는 경우 세 번째 이후의 인수로 전달할 수 있다.
  • setTimeout 함수는 생성된 타이머를 식별할 수 있는 고유한 타이머 id를 반환한다. 브라우저 환경인 경우 숫자를 반환하고, Node.js 환경인 경우 객체를 반환한다.
  • setTimeout 함수가 반환한 타이머 id를 clearTimeout 함수의 인수로 전달하여 타이머를 취소할 수 있다.
    • clearTimeout 함수는 호출 스케줄링을 취소한다.
const timerId = setTimeout(() => console.log("Hi!"), 1000);
// setTimeout 함수가 반환한 타이머 id를 clearTimeout 함수의 인수로 전달하여 타이머를 취소한다.
// 타이머가 취소되면 setTimeout 함수의 콜백 함수가 실행되지 않는다.
clearTimeout(timerId);

setInterval / clearInterval

  • setInterval 함수는 두 번째 인수로 전달받은 시간으로 반복 동작하는 타이머를 생성한다.
  • 타이머가 만료될 때마다 첫 번째 인수로 전달받은 콜백 함수가 반복 호출된다.
    • 타이머가 취소될 때까지 계속된다.
  • clearInterval 함수로 setInterval 함수가 반환한 타이머 id를 전달하여 타이머를 취소할 수 있다.

디바운스와 스로틀

  • scroll, resize, input, mouse move 같은 이벤트는 짧은 시간 간격으로 연속해서 발생하기 때문에 이러한 이벤트에 바인딩한 이벤트 핸들러는 과도하게 호출되어 성능에 문제를 일으킬 수 있다.
  • 디바운스와 스로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 과도한 이벤트 핸들러의 호출을 방지하는 프로그래밍 기법이다.

디바운스

  • 디바운스는 짧은 시간 간격으로 이벤트가 연속해서 발생하면 이벤트 핸들러를 호출하지 않다가 일정한 시간이 경과한 이후에 한 번만 호출되도록 한다.
  • 즉, 짧은 시간 간격으로 발생하는 이벤트를 그룹화해서 마지막에 한 번만 이벤트 핸들러가 호출되도록 한다.
  • 예를 들어 Ajax 요청 같은 무거운 처리를 수행한다면, 사용자가 입력을 완료했을 때 한 번만 Ajax 요청을 전송하는 것이 바람직하다.
const debounce = (callback, delay) => {
  let timerId;

  return (event) => {
    if (timerId) clearInterval(timerId);
    timerId = setTimeout(callback, delay, event);
  };
};
  • debounce 함수는 timerId를 기억하는 클로저를 반환한다.
  • delay가 경과하기 이전에 이벤트가 발생하면 이전 타이머를 취소하고 새로운 타이머를 재설정한다.
  • 따라서 delay보다 짧은 간격으로 이벤트가 발생하면 callback은 호출되지 않는다.
  • resize 이벤트 처리나 input 요소에 입력된 값으로 ajax 요청하는 입력 필드 자동완성 UI 구현, 버튼 중복 클릭 방지 처리등에 유용하게 사용된다.

스로틀

  • 스로틀은 짧은 시간 간격으로 이벤트가 연속해서 발생하더라도 일정 시간 간격으로 이벤트 핸들러가 최대 한 번만 호출되도록 한다.
  • 즉, 스로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 일정 시간 단위로 이벤트 핸들러가 호출되도록 호출 주기를 만든다.
  • delay가 경과하기 이전에 이벤트가 발생하면 아무것도 하지 않다가 delay 시간이 경과했을 때 이벤트가 발생하면 콜백 함수를 호출하고 새로운 타이머를 재설정한다.
  • 따라서 delay 시간 간격으로 콜백 함수가 호출된다.
  • scroll 이벤트 처리나 무한 스크롤 UI 구현 등에 유용하게 사용된다.
const throttle = (callback, delay) => {
  let timerId;

  return (event) => {
    if (timerId) return;
    timerId = setTimeout(
      () => {
        callback(event);
        timer = null;
      },
      delay,
      event
    );
  };
};

비동기 프로그래밍

동기 처리와 비동기 처리

  • 실행 컨텍스트 스택에 함수 실행 컨텍스트가 푸시되는 것은 바로 함수 실행의 시작을 의미한다.
  • 함수가 호출된 순서대로 순차적으로 실행되는 이유는 함수가 호출된 순서대로 함수 실행 컨텍스트가 실행 컨텍스트 스택에 푸시되기 때문이다.
  • JS 엔진은 단 하나의 실행 컨텍스트 스택을 갖기에 함수를 실행할 수 있는 창구가 단 하나이며, 동시에 2개 이상의 함수를 실행할 수 없다.
  • 이처럼 자바스크립트 엔진은 한 번에 단 하나의 태스크만 실행할 수 있는 싱글 스레드 방식으로 동작한다.
  • 싱글 스레드 방식은 한 번에 하나의 태스크만 실행할 수 있기 때문에 처리에 시간이 걸리는 태스크를 실행하는 경우 블로킹(작업 중단)이 발생한다.
  • 이처럼 현재 실행 중인 태스크가 종료할 때까지 다음에 실행될 태스크가 대기하는 방식을 동기 처리라고 한다.
  • 동기 처리 방식은 태스크가 순서대로 하나씩 처리하므로 실행 순서가 보장된다는 장점이 있지만, 앞선 태스크가 종료할 때까지 이후 태스크들이 블로킹되는 단점이 있다.

  • setTimeout 함수는 일정 시간이 경과한 이후에 콜백 함수를 호출하지만 setTimeout 함수 이후의 태스크를 블로킹하지 않고 곧바로 실행한다.
  • 이처럼 현재 실행 중인 태스크가 종료되지 않은 상태라 해도 다음 태스크를 곧바로 실행하는 방식을 비동기 처리라고 한다.
  • 비동기 처리 방식은 현재 실행 중인 태스크가 종료되지 않은 상태라 해도 다음 태스크를 곧바로 실행하므로 블로킹이 발생하지 않는다는 장점이 있지만, 태스크의 순서가 보장되지 않는 단점이 있다.
  • 비동기 함수는 전통적으로 콜백 패턴을 사용한다. 이러한 패턴은 콜백 헬을 발생시켜 가독성을 나쁘게 하고, 비동기 처리 중 발생한 에러의 예외 처리가 곤란하며, 여러 개의 비동기 처리를 한 번에 처리하는 데도 한계가 있다.
  • 타이머 함수인 setTimeout과 setInterval, HTTP 요청, 이벤트 핸들러는 비동기 처리 방식으로 동작한다.

이벤트 루프와 태스크 큐

자바스크립트의 특징 중 하나는 싱글 스레드로 동작하여 한 번에 하나의 태스크만 처리할 수 있다. 하지만 브라우저가 동작하는 것을 살펴보면 많은 태스크가 동시에 처리되는 것처럼 느껴진다.

  • 이처럼 자바스크립트의 동시성을 지원하는 것이 바로 이벤트 루프다.
    • 이벤트 루프는 브라우저에 내장되어 있는 기능 중 하나다.
  • V8 JS 엔진을 비롯한 대부분의 JS 엔진은 크게 2개의 영역으로 구분할 수 있다.
    • 콜스택
      • 실행 컨텍스트 스택이 바로 콜 스택이다.
      • JS 엔진은 하나의 콜 스택을 사용하기 때문에 최상위 실행 컨텍스트가 종료되어 콜 스택에서 제거되기 전까지는 다른 어떤 태스크도 실행되지 않는다.
      • 힙은 객체가 저장되는 메모리 공간이다. 콜 스택 요소인 실행 컨텍스트는 힙에 저장된 객체를 참조한다.
      • 객체는 원시 값과는 달리 크기가 정해져 있지 않아 할당해야 할 메모리 공간의 크기를 런타임에 결정해야 한다.
      • 따라서 객체가 저장되는 힙은 구조화되어 있지 않다는 특징이 있다.
  • 이처럼 콜 스택과 힙으로 구성되어 있는 JS 엔진은 단순히 태스크가 요청되면 콜 스택을 통해 요청된 작업을 순차적으로 실행할 뿐이다.
  • 비동기 처리에서 소스코드의 평가와 실행을 제외한 모든 처리는 브라우저 또는 Node.js가 담당한다.
    • 태스크 큐
      • setTimeout이나 setInterval 같은 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역
      • 태스크 큐와는 별도로 프로미스의 후속 처리 메서드의 콜백 함수가 일시적으로 보관되는 마이크로태스크 큐도 존재한다.
    • 이벤트 루프
    • 콜 스택에 현재 실행 중인 실행 컨텍스트가 있는지, 그리고 태스크 큐에 대기 중인 함수(콜백 함수, 이벤트 핸들러 등)가 있는지 반복해서 확인한다.
    • 만약 콜 스택이 비어 있고 태스크 큐에 대기 중인 함수가 있다면 이벤트 루프는 순차적으로 태스크 큐에 대기 중인 함수를 콜 스택으로 이동시킨다.
  • 비동기 함수인 setTimeout의 콜백 함수는 태스크 큐에 푸시되어 대기하다가 콜 스택이 비게 되면, 다시 말해 전역 코드 및 명시적으로 호출된 함수가 모두 종료하면 비로소 콜 스택에 푸시되어 실행된다.
  • 싱글 스레드 방식으로 동작하는 것은 브라우저가 아니라 브라우저에 내장된 JS 엔진이라는 것에 주의하자. 만약 모든 JS 코드가 자바스크립트 엔진에서 싱글 스레드 방식으로 동작하면 자바스크립트는 비동기로 동작할 수 없다.
    • 즉, 자바스크립트 엔진은 싱글 스레드로 동작하지만 브라우저는 멀티 스레드로 동작한다.

1024

일 - 알고리즘

프로그래머스 Level2 - 삼각 달팽이

function solution(n) {
  const answer = Array.from({ length: n }, (_, i) => Array(i + 1).fill(0));
  let value = 1;
  let mode = 1;
  let i = 0;
  let j = 0;

  for (let cnt = n; cnt > 0; cnt--) {
    if (mode === 1) {
      for (let k = 0; k < cnt; k++) {
        answer[i++][j] = value++;
      }
      i--;
      j++;
      mode = 2;
    } else if (mode === 2) {
      for (let k = 0; k < cnt; k++) {
        answer[i][j++] = value++;
      }
      j--;
      mode = 3;
    } else {
      for (let k = 0; k < cnt; k++) {
        answer[--i][--j] = value++;
      }
      i++;
      mode = 1;
    }
  }
  return answer.flat();
}
  • 최근에는 구현 위주 문제들로 문제풀이를 진행하고 있다.
  • 한 줄씩 이동할 때마다 이동하는 거리가 줄어든다는 규칙성을 발견하고 이를 이용하여 위에서 아래로 내려오는 경우, 왼쪽에서 오른쪽으로 가는 경우, 아래에서 위로 가는 경우 세 모드로 나눠서 코드를 구현했다.

프로그래머스 Level2 - 행렬 테두리 회전하기

function solution(rows, columns, queries) {
  let answer = [];
  const arr = Array.from(Array(rows + 1), () => Array(columns + 1).fill(0));

  for (let i = 0; i <= rows; i++) {
    for (let j = 0; j <= columns; j++) {
      arr[i][j] = (i - 1) * columns + j;
    }
  }

  for (const query of queries) {
    let min = Number.MAX_SAFE_INTEGER;
    const [x1, y1, x2, y2] = query;
    const [xy1, xy2, xy3, xy4] = [
      arr[x1][y1],
      arr[x1][y2],
      arr[x2][y2],
      arr[x2][y1],
    ];

    for (let j = y2; j > y1; j--) {
      arr[x1][j] = arr[x1][j - 1];
      min = Math.min(min, arr[x1][j - 1]);
    }

    for (let i = x2; i > x1; i--) {
      arr[i][y2] = arr[i - 1][y2];
      min = Math.min(min, arr[i - 1][y2]);
    }

    for (let j = y1; j < y2; j++) {
      arr[x2][j] = arr[x2][j + 1];
      min = Math.min(min, arr[x2][j + 1]);
    }

    for (let i = x1; i < x2; i++) {
      arr[i][y1] = arr[i + 1][y1];
      min = Math.min(min, arr[i + 1][y1]);
    }

    arr[x1 + 1][y2] = xy2;
    arr[x2][y2 - 1] = xy3;
    arr[x2 - 1][y1] = xy4;
    min = Math.min(min, ...[xy2, xy3, xy4]);

    answer.push(min);
  }

  return answer;
}
  • 구현 문제들을 어려운 문제들도 하나씩 풀어감에 따라 자신감이 점점 오르는걸 느낀다.
  • 네 방향으로 이동하는 것을 꼭짓점의 좌표를 이용해서 구현하였다.

태그:

카테고리:

업데이트: