티스토리 뷰
🔥🔥 표시: 개인적으로 어렵게 느낀 질문 체크
🔥🔥Q.자바스크립트의 특징은 무엇인가요? (싱글 스레드), 싱글 스레드와 멀티 스레스의 장단점은 무엇인가요?
자바스크립트는 동적이며, 타입을 명시할 필요가 없는 인터프리터 언어입니다. 또한, 자바스크립트 엔진은 기본적으로 하나의 쓰레드에서 동작합니다. 하나의 쓰레드(= 싱글 stack)에서 동작한다는 의미는 동시에 하나의 작업만 할 수 있다는 것을 의미합니다.
싱글 스레드의 장점은 스레드 간의 자원 공유가 없기 때문에 그에 대한 동기화에 대해 신경쓰지 않아도 되는 점과 스레드 간의 context switching이 일어나지 않으므로 오버헤드를 방지할 수 있다는 점입니다. 단점은 여러개의 cpu를 활용할 수 없습니다. 멀티스레드의 장점은 프로세스의 자원과 상태를 스레드 간에 공유하면서 효율적인 실행과 스레드 간의 통신이 가능합니다. 스레드의 context switching의 경우 작업 전환이 빠릅니다. 단점은 공유 자원에 대한 접근을 제어해주는 작업이 필요합니다. 또한, 공유 메모리를 사용하기 때문에 하나의 스레드가 죽으면 전체 스레드에 영향을 미치게 됩니다.
싱글 vs 멀티 스레드 단순화된 답변ver)
싱글 스레드의 장점은 개발자가 동시성에 의해 발생되는 복잡한 문제들을 고려하지 않고도 쉽게 서비스를 만들 수 있다는 것입니다. 멀티 스레드로 실행되는 언어면 웹에서 발생하는 동시성 문제에 대해 해결해야 하는데 JS는 교착 상태같은 다중 스레드 환경에서 발생하는 문제에 대해 신경쓸 필요가 없고 비동기를 통해 쉽게 여러 요청을 처리할 수 있습니다. 멀티스레드는 상대적으로 사용하기 어려운 단점을 가지고 있지만 메모리 공간과 시스템 자원 소모가 줄어들고 시스템 처리량이 향상되는 장점을 지니고 있습니다.
+추가로 알면 좋은 내용: 인터프리터 언어 명령 자체의 속도는 한줄 한줄 처리하기 때문에 컴파일러 언어에 비해 느리지만 대부분의 모던 JavaScript 인터프리터들은 사실 JIT(Just-in-time 컴파일)*이라는 기술을 사용해 성능을 향상하기 때문에 속도면에서 떨어지진 않는다고 하네요.
- 인터프리터 언어 - 코드를 위에서 아래로 실행하고, 코드의 구동 결과를 즉시 반환한다. (번역과 실행이 동시에 이루어지며 한줄한줄 바로 실행, 컴파일 과정 필요X)
- 컴파일 언어 - 컴퓨터가 코드를 실행하기 전에 다른 형태로 변환(컴파일)과정이 필요하다. 컴파일러로 기계언어로 변환하여 그 결과를 컴퓨터가 실행하는 것-> 프로그램이 원본 소스 코드에서 생성한 이진 형식(바이너리)으로부터 실행된다. ( 소스코드를 한번에 다른 목적 코드로 번역 후, 한 번에 실행하는 프로그램언어)
- 컴파일러(compiler) - 특정 프로그래밍 언어를 다른 프로그래밍 언어로 옮기는 프로그램
- JIT(Just-in-time 컴파일) - 스크립트의 실행과 동시에 소스 코드를 더 빠르게 실행할 수 있는 이진 형태로 변환하여 최대한 높은 실행 속도를 얻는 방법
싱글스레드 vs 멀티 스레드
▶싱글 스레드의 장점
자원 접근에 대한 동기화를 신경쓰지 않아도 됩니다. 여러개의 스레드가 공유된 자원을 사용할 경우, 각 스레드가 원하는 결과를 얻게 하려면 공용 자원에 대한 접근이 통제되어야 하는데 이 과정에서 프로그래머의 노력과 많은 비용이 발생하게 됩니다.
또한, 문맥 교환(context switch) 작업도 필요없게 됩니다. 작업 전환은 여러 개의 프로세스가 하나의 프로세서를 공유할 때 발생하는 작업으로 많은 비용을 필요로 합니다.
단순히 CPU만을 사용하는 계산 작업이라면, 멀티스레드보다 효율적입니다.
프로그래밍 난이도가 쉽고, CPU, 메모리를 적게 사용합니다.(비용도 적게 든다.)
▶싱글 스레드의 단점
연산량이 많은 작업을 하는 경우, 그 작업이 완료되어야 다른 작업을 수행할 수 있습니다.
싱글 스레드 모델은 에러 처리를 못하는 경우 멈추게 됩니다. ( 화면이 하얗게 표시 )
▶멀티 스레드의 장점
응답성: 프로그램의 일부분(스레드 중 하나)이 중단되거나 긴 작업을 수행하더라도 프로그램의 수행이 계속되어 사용자에 대한 응답성이 증가합니다.
경제성: 프로세스 내 자원들과 메모리를 공유하기 때문에 메모리 공간과 시스템 자원 소모가 줄어듭니다. 스레드간 통신이 필요한 경우에도 쉽게 데이터를 주고 받을 수 있으며 프로세스의 context switching과 달리 스레드 간의 context switching은 캐시 메모리를 비울 필요가 없기 때문에 더 빠릅니다.
멀티프로세서의 활용: 다중 CPU구조에서는 각각의 스레드가 다른 프로세서에서 병렬로 수행될 수 있으므로 병렬성이 증가합니다.
▶멀티 스레드의 단점: 동기화 작업, 문맥교환 등 프로그래밍 난이도가 높습니다. 또한, 스레드의 수만큼 자원을 많이 사용하고 운영체제의 지원이 필요합니다.
Q. Middleware란 무엇인가요?
넓은 의미) 미들웨어는 운영 체제와 해당 운영 체제에서 실행되는 애플리케이션 사이에 존재하는 소프트웨어입니다(서로 다른 애플리케이션이 서로 통신하는 데 사용되는 소프트웨어). 기본적으로 숨겨진 변환 계층으로 기능하는 미들웨어는 분산 애플리케이션의 통신 및 데이터 관리를 가능하게 합니다.
(데이터와 데이터 베이스가 "파이프" 사이를 쉽게 통과할 수 있도록 두 가지 애플리케이션을 연결하기 때문에 플러밍(plumbing: 배관)이라고도 합니다. ) 출처: https://azure.microsoft.com/ko-kr/resources/cloud-computing-dictionary/what-is-middleware/
좁은 의미, 리덕스에서 미들웨어) 리덕스 미들웨어는 액션을 디스패치했을 때 리듀서에서 이를 처리하기에 앞서 사전에 지정된 작업들을 실행합니다. 미들웨어는 액션과 리듀서 함수 사이의 중간자입니다. 리듀서가 액션을 처리하기 전에 미들웨어가 액션을 콘솔에 기록하거나 취소 혹은 다른 종류의 액션을 추가적으로 디스패치(dispatch)하기 등 여러 작업을 할 수 있습니다.
(리덕스 흐름: 액션 -> 미들웨어 -> 리듀서-> 스토어 -> UI컴포넌트 -> 액션(다시 반복) )
Q. 함수 선언문과 함수 표현식은 어떤 차이가 있나요?
함수 선언문과 함수 표현식에서 호이스팅 방식 차이가 있습니다.
->함수 선언문은 호이스팅에 영향을 받지만 함수 표현식은 호이스팅에 영향받지 않습니다.
- 함수 선언문(function declaration)은 함수 이름이 생략 불가하며 가장 베이직한 방법입니다. 런타임 이전에 자바스크립트 엔진에서 먼저 실행되어, 함수 자체를 실행시킬 수 있습니다.
- 함수 표현식(function expression)은 변수 호이스팅과 같이 런타임 이전에 해당 값을 undefined로 초기화만 시키고, 런타임에서 해당 함수 표현식이 할당되어 그때 객체가 되기 때문에 호이스팅이 되지 않습니다.
esay ver )
- 함수 선언식- 함수명이 정의되어 있고, 별도의 할당 명령이 없는 것
- 함수 표현식 - 정의한 function을 별도의 변수에 할당하는 것
- 차이점: 둘의 차이점은 호이스팅에서 발생합니다. 함수 선언식은 함수 전체를 호이스팅합니다. 정의된 범위의 맨 위로 호이스팅되서 함수 선언 전에 함수를 사용할 수 있습니다. 함수 표현식은 별도의 변수에 할당하게 되는데, 변수는 선언부와 할당부를 나누어 호이스팅하게 됩니다. 선언부만 호이스팅하게 됩니다.
// 함수 선언문
function add(x, y) {
return x + y
}
// 함수 표현식
const sub = function(x, y) {
return x + y
}
Q.CSR과 SSR의 차이는 무엇인가요?
차이점으로는 크게 초기 렌더링 속도, SEO 대응, 보안성이 있습니다. CSR는 최초 로딩 시 브라우저가 서버에 HTML, CSS, JS등 모든 리소스를 한 번에 받아오기 때문에 부분 데이터만 받아오는 SSR보다 느립니다. SSR은 서버에서 사용자에게 보여줄 페이지를 모두 구성하기 때문에 HTML에 대한 정보가 처음부터 포함되어 있어서 모든 검색엔진에 대한 SEO(검색 엔진 최적화)가 가능합니다. 마지막으로 CSR은 쿠키나 localStorage에 사용자에 대한 정보를 저장해야 하기 때문에 XSS공격에 취약한 반면, SSR은 사용자 정보를 서버 측에서 세션으로 관리할 수 있어 보안성 차이가 있습니다.
다른 팀원의 답변! 😎
CSR(Client Client Side Rendering)은 사용자의 요청에 따라 필요한 부분만 응답 받아 랜더링하는 방식입니다. 장점은 빠른 속도와 서버 부하 감소가 있습니다. 단점은 첫 페이지 로딩 시 처음부터 모든 코드를 다 불러오기 때문에 SSR에 비해 느리다는 점, 검색엔진 최적화가 잘 되지 않는다는 점이 있습니다. SSR(Server Side Rendering)은 서버에서 데이터까지 모두 포함한 완전하게 만들어진 페이지 전체를 랜더링하는 방식입니다. 장점은 이미 다 그려진 페이지를 랜더링하기 때문에 첫 페이지 로딩이 CSR보다 빠르다는 점, 검색엔진 최적화가 있습니다. 단점은 매번 랜더링이 일어나 페이지 이동시 깜빡거리고, 랜더링에 따른 부하가 발생할 수 있습니다.
SPA(Single Page Application) - 한 개의 페이지(html 파일)을 가진 웹 어플리케이션을 말합니다.
한 개의 페이지인 만큼 새로고침이 일어나지 않고 JS를 통해 동적으로 화면을 바꿀 수 있습니다.
SPA (Single Page Application)란 서버로부터 새로운 페이지를 불러오지 않고 현재의 페이지를 동적으로 다시 작성함으로써 사용자와 소통하는 웹 애플리케이션이나 웹사이트를 말합니다. 즉, 현재의 HTML을 고정하고 변경되는 부분에 대해서만 서버에서 불러와 클라이언트 사이드에서 렌더링하는 방식인 것입니다.
- 어플리케이션 생명 주기 중에서 한번만 리소스를 로딩하고 그 후에는 데이터를 받아올 때만 서버와 통신합니다.
- 페이지 이동 시 기존 페이지 내부를 수정해서 보여주는 방식
- 필요한 부분만 갱신하기에 자연스러운 페이지 이동이 가능합니다.
MPA(Multi Page Application) - SPA와 달리 사용자의 요청마다 웹 서버로부터 완전한 페이지를 가져옵니다.
e.g.) 데이터의 조회 및 수정 시 다른 페이지로 이동
CSR(Client Side Rendering)
- 클라이언트에서 js를 통해 페이지를 렌더링하는 방식을 말합니다. CSR는 사용자의 요청에 따라 필요한 부분만 응답받아 렌더링하는 방식입니다.(브라우저가 필요에 따라서 동적으로 렌더링을 하는데 결국 서버와는 데이터(JSON)통신 밖에 하지 않게 됩니다. )
- 장점으로 빠른 속도와 서버 부하 감소가 있습니다.
- SPA(Single Page Application)형식의 대부분의 프론트엔드
- 라이브러리 (Vue, React)가 CSR방식으로 만들어졌습니다. ->SPA가 CSR 방식을 사용한 것입니다.
- 초기 렌더링 시간 문제, SEO문제, 메타 데이터 동적 생성의 한계를 극복하기 위해 SSR 프레임워크 (Next.js 등)을 보유하고 있습니다
SSR(Server Side Rendering)
말 그대로 서버쪽에서 렌더링 준비를 끝마친 상태로 클라이언트에 전달하는 방식입니다.
- SSR은 서버에서 데이터까지 모두 포함한 완전하게 만들어진 페이지 전체를 렌더링하는 방식
CSR 과 SSR 차이점
웹 페이지 로딩 속도(처음 로딩, 이후 로딩)
- 첫 페이지를 로딩할 때, CSR의 경우 HTML, CSS 외 모든 스크립트(JS)를 한 번에 불러옵니다(모든 리소스를 한 번에 다 가져온다). 반면 SSR은 필요한 부분의 HTML과 스크립트만 불러오기 때문에 평균적으로 SSR이 더 빠릅니다. -> webpack의 code splitting으로 해결할 수 있다고 합니다. e.g) React에서의 code splitting: React 16.6 부터는 lazy와 Suspense를 이용하여 코드 스플리팅을 할 수 있다.(아직 ssr에서는 지원되지 않는다.)
- 나머지 데이터를 로딩할 경우, CSR는 맨 처음에 필요한 모든 자원을 가져와있는 상태이므로 변화가 필요한 만큼의 데이터만 요청합니다(e.g.JSON). (첫 페이지 로딩 이후, 빠른 렌더링으로 사용자 경험(UX가 좋다). 반면, SSR은 첫 페이지를 로딩한 과정을 정확하게 다시 실행합니다(새로고침 발생)
SEO(검색 엔진 최적화) 대응
- 검색 엔진은 자동화된 로봇인 '크롤러'로 웹 사이트를 읽는다. CSR은 자바스크립트를 실행시켜 동적으로 컨텐츠가 생성되기 때문에 자바스크립트가 실행 되어야 metadata가 바뀌었다.
- CSR은 가장 처음에 html파일을 가질 때 파일에 body 태그 하나만 들어있는 첫 페이지가 깡통인 상태가 됩니다. 이런 특성 때문에 웹 크롤러에서 CSR로 구성된 페이지를 접속하면 검색 노출이 잘 되지 않습니다.
- SSR은 CSR보다 SEO측면에서 유리합니다. 서버에서 사용자에게 보여줄 페이지를 모두 구성한 다음 사용자에게 보여주기 때문에 CSR의 단점인 깡통 페이지를 극복할 수 있습니다.
서버 자원 이용
- CSR은 최초 호출 때만 html, js, css를 요청하기 때문에 백엔드 호출을 최소화할 수 있습니다(필요한 만큼의 데이터만 요청). -> 서버 부하 최소화
- SSR은 매번 서버에 요청을 하기 때문에 서버 자원을 더 많이 사용합니다. -> 서버 부하
보안성
- CRS는 쿠키나 localStorage에서 사용자에 대한 정보를 저장해야 하기 때문에 XSS 공격에 취약합니다
- 반면, SSR은 사용자 정보를 서버 측에서 세션으로 관리할 수 있어 보안성 차이가 있습니다.
Q. map이나 list를 사용해서 렌더링할 때 key값을 할당해야 하는 이유는 무엇인가요?
브라우저가 변화를 감지하여 realDOM이 변경되는 과정*에서 key가 엘리먼트의 변화를 감지하는 역할을 하기 때문입니다. key가 없을 때는 리스트를 순차적으로 비교하며 변화를 감지합니다(비효율적). 하지만 key가 존재할 땐 리액트는 realDOM과 비교하여 어떤 요소만 변화가 있는지 빠르게 찾아낼 수 있습니다. key를 사용하면 기존의 요소는 리렌더링하지 않고 변화가 감지된 요소만 리렌더링하여 효율적인 DOM사용이 가능합니다.
->요약: key를 사용하는 이유는 엘리먼트 혹은 컴포넌트의 변화를 감지하기 위함이고 그 이유는 효율적인 DOM사용으로 귀결됩니다.
렌더링에는 전혀 문제가 없지만 공식문서에 의하면, key는
- React가 어떤 항목을 변경, 추가 또는 삭제할 지 식별하는 것을 돕고
- 엘리먼트에 안정적인 고유성을 보유하기 위해 배열 내부의 엘리먼트에 지정해야 되는 것
-> key는 변하지 않는 유일한 식별자의 역할을 한다.( key 값은 문자열을 사용하는 것이 좋습니다. 대부분 데이터의 id를 key로 사용합니다.)
- DOM 노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성합니다.
- virtualDOM에서 변화를 감지하고 변화가 일어난 virtualDOM과 realDOM을 비교하여 새로운 DOM을 연산합니다.-> realDOM은 virtualDOM과 동일하게 업데이트 됩니다.(realDOM은 모든 부분을 업데이트 하는 것이 아니라 변화된 부분만 감지하여 업데이트한다. 그렇기 때문에 최소한의 업데이트만 줄 수 있다면 DOM은 효율적으로 운용될 수 있는 것이죠.)
Q.프레임워크와 라이브러리의 차이점?
프레임워크와 라이브러리의 차이점은 "제어 흐름"의 권한이 어디에 있는가입니다.
라이브러리를 사용할 때 사용자는 애플리케이션 코드의 흐름을 직접 제어합니다. 개발 시 필요한 기능이 있을 경우,
능동적으로 라이브러리를 호출하여 사용하거나 기존에 구성된 함수나 코드를 가져다 써야 합니다.
반면, 프레임워크는 애플리케이션의 코드가 프레임워크에 의해 사용됩니다. 애플리케이션 코드는 프레임워크가 짜 놓은
틀에서 수동적으로 동작하기 때문에 제어의 흐름은 프레임워크가 가지고 있고 사용자가 그 안에 필요한 코드를 작성하게 됩니다.
제어의 역전(IoC, Inversion of Control)
어떤 일을 하도록 만들어진 Framework가 Control 권한을 위임하는 것을 의미하는데, 간단히 말하면 프로그램의 제어 프름 구조가 뒤바뀐 것을 말합니다.
라이브러리의 경우 애플리케이션의 흐름을 사용자가 직접 제어하지만 프레임워크의 경우 코드를 연결할 수 있는 위치를 제공하고 필요에 따라 사용자가 연결한 코드를 호출하는 제어 흐름 권한을 가지고 있습니다.
🔥🔥Q. 생명주기 메소드에 대해서 설명해주세요.
LifeCycle Method(생명주기 메서드)는 컴포넌트가 브라우저 상에 나타나고, 업데이트되고, 사라지게 될 때 호출되는 메서드들이며 클래스형 컴포넌트에서만 사용할 수 있습니다. 컴포넌트의 생명주기 대로 설명하자면 브라우저 상에 처음 나타날 때 constructor()-> render()-> componentDidMount() 순으로 실행되며 DidMount는 첫 렌더링시에만 실행됩니다. 이후 컴포넌트가 업데이트 될 때, componentDidupdate() 가 실행됩니다. 이 단계에서는 이미 가상돔이 진짜 돔에 올라간 뒤이므로 ref 관련 작업을 처리해도 됩니다. 마지막으로 componentWillUnmount() 메서드는 컴포넌트가 제거되기 직전에 실행됩니다.
LifeCycle Method(생명주기 메소드)는 컴포넌트가 브라우저 상에 나타나고, 업데이트되고, 사라지게 될 때 호출되는 메서드들 입니다. 클래스형 컴포넌트에서만 사용할 수 있습니다. (리액트 16.8버전부터 등장한 React Hooks로 라이프 사이클 함수를 대체할 수 있기 때문입니다.)
- constructor() -생성자 함수 컴포넌트가 생성되면 가장 처음 호출됩니다.
- render() -컴포넌트의 모양을 정의합니다. return JSX코드 (여기서 state나 props를 건드려 데이터를 수정하면 안됩니다.)
- componentDidMount() - 컴포넌트가 화면에 나타나는 것을 마운트한다 라고 표현합니다. 첫번째 렌더링을 마친 후 딱 한번만 실행됩니다. ajax요청, 이벤트 등록, 함수 호출 등을 처리합니다. (가상 돔이 실제돔으로 올라간 뒤이기 때문에 ref관련 작업을 render이후에 처리해도 됩니다.)
- componentDidUpdate(prevProps, prevState, snapshot) - 리렌더링을 완료한 후 실행되는 함수입니다. 파라미터는 각각 업데이트 이전의 props, state 값입니다. 이전 데이터와 비교할 일이 있을 때 씁니다. 이때도 마찬가지로 실제 돔에 올라간 상태라 DOM관련 처리를 해도 됩니다.
- 수정(업데이트)는 사용자의 행동(클릭, 데이터 입력 등)으로 데이터가 바뀌거나, 부모 컴포넌트가 렌더링할 때 업데이트 됩니다.
- props/ state가 바뀔 때, 부모 컴포넌트가 업데이트 됐을 때, 강제로 업데이트(forceUpdate()를 통해 강제 컴포넌트 업데이트)
- componentWillUnmount() - 삼항 연산자를 사용해서 컴포넌트를 보여주거나 없애는 것을 조건부렌더링이라고 부릅니다. 해당 함수는 부모 컴포넌트가 자식 컴포넌트를 제거할 때(제거는 페이지를 이동하거나, 사용자의 행동으로 인해 컴포넌트가 화면에서 사라지는 것을 의미) 작동합니다.
import React, { Component } from "react";
//클래스의 경우 -> constructor(render함수 위에 부분이 여기에 해당) --> render -> ref(레프를 달아놨다면) -> componentDidMount
//-> (setState/props 바뀔 때) -> *shouldComponentUpdate(true)* -> 리render -> componentDidUpdate
//부모가 나를 없앴을 때(안 없애면 해당없음) -> componentWillUnmount -> 소멸
class RSP extends Component {
state = {
result: "",
imgCoord: 0,
score: 0,
};
//처음 컴포넌트 렌더가 성공적으로 실행됐다면 아래 코드가 실행된다.(JSX코드가 렌더링 성공 뒤에 componentDidMount 실행)
componentDidMount() {
//컴포넌트가 첫 렌더링된 후
}
componentDidUpdate() {
//컴포넌트 리렌더링 후
}
componentWillUnmount() {
//컴포넌트가 제거되기 직전(부모가 자식인 나를 없앴을 때)
}
render() {
const { result, score, imgCoord } = this.state;
return (
<>
<div
id="computer"
style={{
background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0`,
}}
>
<div>
<button
id="rock"
className="btn"
></button>
<button
id="scissor"
className="btn"
></button>
<button
id="paper"
className="btn"
></button>
</div>
<div>{result}</div>
<div>현재 {score}점</div>
</div>
</>
);
}
}
export default RSP;
출처: 제로초님
'Frontend > WIL😎' 카테고리의 다른 글
[HIL | 항해99] 프론트엔드 항해99 과연 좋은 선택이었을까? (0) | 2022.08.21 |
---|---|
[FE면접] 질문 모음 02 (0) | 2022.08.18 |
[WIL | 항해99] 마지막주차 회고 실전 프로젝트 끝!( feat. FE tip) (1) | 2022.08.10 |
[WIL | 항해99] 7주차 회고 미니프로젝트 끝 실전 시작 (0) | 2022.06.26 |
[WIL | 항해99] 4주차 회고 라이프사이클(클래스형 vs 함수형), react hooks (0) | 2022.06.05 |
- Total
- Today
- Yesterday
- 프리렌더링확인법
- 원티드 FE 프리온보딩 챌린지
- 타입스크립트 장점
- D 플래그
- grid flex
- float 레이아웃
- 원티드 프리온보딩 FE 챌린지
- 항해99프론트후기
- nvm 설치순서
- getServerSideProps
- fs모듈 넥스트
- Prittier
- 형제 요소 선택자
- 틸드와 캐럿
- 부트캠프항해
- && 셸 명령어
- nvm경로 오류
- tilde caret
- 타입스크립트 DT
- getStaticPaths
- aspect-ratio
- text input pattern
- ~ ^
- 원티드 3월 프론트엔드 챌린지
- reactAPI
- 원티드 프리온보딩 프론트엔드 챌린지 3일차
- 항해99프론트
- 항해99추천비추천
- 프리온보딩 프론트엔드 챌린지 3월
- is()
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |