반응형

# 스프링 핵심 원리 기본편

  • 표준 인터페이스 : JPA
  • JPA 구현체들 : 하이버네이트, EclipseLink, 기타 등등..

# 스프링의 역사_전설의 시작

  • 2002년 로드 존슨 책 출간
  • EJB의 문제점 지적
  • EJB 없이도 충분히 고품질의 확장 가능한 애플리케이션을 개발할 수 있음을 보여주고, 30,000라인 이상의 기반 기술을 예제 코드로 선보임
  • 여기에 지금의 스프링 핵심 개념과 기반 코드가 들어가 있다.
  • BeanFactory, ApplicationContext, POJO, 제어의 역전, 의존관계 주입
  • 책이 유명해지고, 개발자들이 책의 예제 코드를 프로젝트에 사용.
  • 책 출간 직후 '유겐 휠러', '얀 카로프'가 로드 존슨에게 오픈소스 프로젝트를 제안
  • 스프링의 핵심 코드의 상당수는 '유겐 휠러'가 지금도 개발
  • 스프링 이름은 전통적은 J2EE(EJB)라는 겨울을 넘어 새로운 시작이라는 뜻으로 지음 스프링(Spring)을 만들게 됨

 

# 스프링의 역사_릴리즈

  • 2003년 스프링 프레임워크 1.0 출시 - XML
  • 2006년 스프링 프레임워크 2.0 출시 - XML 편의 기능 지원
  • 2009년 스프링 프레임워크 3.0 출시 - 자바 코드로 설정
  • 2013년 스프링 프레임워크 4.0 출시 - 자바8
  • 2014년 스프링 부트 1.0 출시
  • 2017년 스프링 프레임워크 5.0, 스프링 부트 2.0 출시 - 리엑티브 프로그래밍 지원
  • 2020년 09월 현재 스프링 프레임워크 5.2.x, 스프링 부트 2.3.x

 

# 스프링이란?

## 스프링의 생태계

  • 스프링은 특정한 '하나'가 아니라 '여러 기술'들의 모음이다.
  • 스프링의 가장 핵심 : 스프링 프레임워크
  • 여러 스프링의 기술들을 편리하게 사용할 수 있도록 도와주는 것 : 스프링 부트
  • 그외 선택 항목 : 스프링 데이터, 스프링 세션, 스프링 시큐리티, 스프링 Rest Docs, 스프링 배치, 스프링 클라우드, 등등...
  • ( spring.io/projects 에서 다양한 그외 선택 항목들을 볼 수 있다. )

## 스프링 프레임워크

  • 핵심 기술 : 스프링 DI 컨테이너, AOP, 이벤트, 기타
  • 웹 기술 : 스프링 MVC, 스프링 WebFlux
  • 데이터 접근 기술 : 트랜잭션, JDBC, ORM 지원, XML 지원
  • 기술 통합 : 캐시, 이메일, 원격접근, 스케줄링
  • 테스트 : 스프링 기반 테스트 지원
  • 언어 : 코틀린, 그루비
  • 최근에는 스프링 부트를 통해서 스프링 프레임워크의 기술들을 편리하게 사용

## 스프링 부트

  • 스프링을 편리하게 사용할 수 있도록 지원, 최근에는 기본으로 사용 (스프링 부트만 단독으로 사용할 수 없다, 스프링 프레임워크를 사용하는 환경에서 스프링 부트를 사용하여 도와주는 것.)
  • 단독으로 실행할 수 있는 스프링 애플리케이션을 쉽게 생성
  • Tomcat 같은 웹 서버를 내장해서 별도의 웹 서버를 설치하지 않아도 됨
  • 손쉬운 빌드 구성을 위한 starter 종속성 제공
  • 스프링과 3rd parth(외부) 라이브러리 자동 구성
  • 메트릭, 상태 확인, 외부 구성 같은 프로덕션 준비 기능 제공
  • 관례에 의한 간결한 설정

## 스프링 단어?

  • 스프링이라는 단어는 문맥에 따라 다르게 사용된다.
스프링 DI 컨테이너 기술

스프링 프레임워크

스프링 부트, 스프링 프레임워크 등을 모두 포함한 스프링 생태계

## 스프링을 만든 이유?

### 스프링의 핵심 개념, 핵심 컨셉?

  • 웹 애플리케이션을 만들고, DB 접근 편리하게 해주는 기술?
  • 전자정부 프레임워크?
  • 웹 서버도 자동으로 띄워주고?
  • 클라우드, 마이크로서비스?
  • 위 사항들은 핵심이 아닌 '결과물'

### 스프링의 진짜 핵심

  • 스프링은 자바 언어 기반의 프레임워크
  • 자바 언어의 가장 큰 특징 : '객체 지향 언어'
  • 스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임워크
  • 정리 : 스프링은 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크.

 

# 좋은 객체 지향 프로그래밍?

## 객체 지향의 특징

  • 추상화
  • 캡슐화
  • 상속
  • 다형성 (Polymorphism : 객체 지향의 핵심!!)

## 객체 지향 프로그래밍

  • 객체 지향 프로그래밍은 컴퓨터 프로그램을 명렁어의 목록으로 보는 시각에서 벗어나 여러개의 독립된 단위, 즉 '객체들의 모임'으로 파악하고자 하는 것이다. 각각의 객체는 '메시지'를 주고받고, 데이터를 처리할 수 있다. (협력)
  • 객체 지향 프로그래밍은 프로그램을 '유연'하고 '변경'이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.

## 유연하고 변경이 용이하다?

  • 레고 블럭을 조립하듯이...
  • 키보드, 마우스를 갈아 끼우듯이...
  • 컴퓨터 부품을 갈아 끼우듯이...
  • 컴포넌트를 쉽고 유연하게 변경하면서 개발할 수 있는 방법

### 다형성의 실세계 비유

  • 실세계와 객체 지향을 1:1로 매칭할 순 없다. (맞지 않음)
  • 그래도 실세계의 비유로 이해하기에는 좋음.
  • 역할과 구현으로 세상을 구분
  • 예시
운전자 - 자동차

공연 무대

키보드, 마우스, 세상의 표준 인터페이스들

정렬 알고리즘

할인 정책 로직

### 다형성의 예제 1

  • 자동차의 '역할'을 3개(K3, 아반떼, 테슬라 모델3)의 다른 자동차들이 '구현'.
  • 이때 운전자가 K3를 운행하다가 다른 차로 바꿨을 경우 운전 가능여부 : 가능 (자동차의 '역할'에 대한 '구현'만 바뀌었기 때문)
  • 자동차가 바뀌어도 운전자에게는 영향을 주지 않는다.
  • 운전자(클라이언트)가 자동차의 내부 구조를 몰라도 된다. 자동차의 구조가 내부적으로 바뀌더라도 자동차의 역할만 하고 있으면 운전자에게 영향을 주지 않는다. 
  • 클라이언트에게 영향을 주지 않고, 새로운 기능을 제공할 수 있다. 이는 역할과 구현으로 세상을 구분했기 때문에 가능하다.

### 다형성의 예제 2

  • 공연을 할 때에는 배우는 대체가 가능해야 한다.
  • '역할'과 '구현'을 나누면, 변경 가능한 '대체 가능성'이 생긴다.
  • 이게 바로 '유연하고 변경이 용이함'

## 역할과 구현을 분리

  • 역할구현으로 구분하면 세상이 단순해지고, 유연해지며 변경도 편리해진다.
  • 역할과 구현을 분리 하는것의 장점
'클라이언트'는 대상의 역할(인터페이스)만 알면 된다.

'클라이언트'는 구현 대상의 '내부 구조를 몰라도' 된다.

'클라이언트'는 구현 대상의 '내부 구조가 변경'되어도 영향을 받지 않는다.

'클라이언트'는 구현 '대상 자체를 변경'해도 영향을 받지 않는다.

## 역할과 구현을 분리 (자바 언어)

  • 자바 언어의 다형성을 활용
역할 = 인터페이스

구현 = 인터페이스를 구현한 클래스, 구현 객체
  • 객체를 설계할 때 역할과 구현을 명확히 분리
  • 객체 설계시 역할(인터페이스)을 먼저 부여하고, 그 역할을 수행하는 구현 객체 만들기

## 객체의 협력이라는 관계부터 생각

  • 혼자 있는 객체는 없다.
  • 클라이언트 : 요청 / 서버 : 응답
  • 수 많은 객체 클라이언트와 객체 서버는 서로 협력 관계를 가진다.

## 자바 언어의 다형성

  • 오버라이딩을 떠올려보자
# 메소드 오버라이딩
서브 클래스 또는 자식 클래스가 자신의 슈퍼클래스들 또는 부모 클래스들 중 하나에 의해
이미 제공된 메소드를 특정한 형태로 구현하는 것을 제공하는 언어의 특징
  • 오버라이딩은 자바 기본 문법
  • 오버라이딩 된 메서드가 실행
  • 다형성으로 인터페이스를 구현한 객체를 실행 시점에 유연하게 변경할 수 있다.
  • 물론 클래스 상속 관계도 다형성, 오버라이딩 적용 가능

## 다형성의 본질

  • 인터페이스를 구현한 객체 인스턴스를 실행 시점유연하게 변경할 수 있다.
  • 다형성의 본질을 이해하려면 협력이라는 객체사이의 관계에서 시작해야 한다.
  • 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다.

## 역할과 구현을 분리_정리

  • 실세계의 역할과 구현이라는 편리한 컨셉을 다형성을 통해 객체 세상으로 가져올 수 있다.
  • 이로인해 유연하고, 변경이 용이, 확장 가능한 설계, 클라이언트에 영향을 주지 않는 변경 가능, 인터페이스를 안정적으로 잘 설계하는 것이 중요하다.

## 역할과 구현을 분리_한계점

  • 역할(인터페이스) 자체가 변하면, 클라이언트, 서버 모두에 큰 변경이 발생한다.
  • 자동차를 비행기로 변경해야 한다면?
  • 대본 자체가 변경된다면?
  • USB 인터페이스가 변경된다면?
  • 인터페이스를 안정적으로 잘 설계하는 것이 중요

 

# 스프링과 객체 지향

  • 다형성이 가장 중요하다. (스프링의 꽃?)
  • 스프링은 다형성을 극대화해서 이용할 수 있게 도와준다.
  • 스프링에서 이야기하는 제어의 역전(IoC), 의존관계 주입(DI)은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수  있도록 지원한다.
  • 스프링을 사용하면 마치 레고 블럭 조립하듯이! 공연 무대의 배우를 선택하듯이! 구현을 편리하게 변경할 수 있다.

 

출처 : 인프런 스프링 핵심 원리 기본편

반응형
반응형

# 자바스크립트 학습

# JSON 오브젝트

## JSON 오브젝트의 개요

  • JavaScript Object Notation의 약어로, 빌트인 오브젝트이다.
  • new 연산자로 인스턴스 생성 불가
  • JSON의 주요 기능
데이터 소웃신의 변환 기준
텍스트이므로 전송 속도가 빠르다.
파일 확장자 : json, tzt도 사용 가능
  • JS 데이터 타입을 지원, 다른 언어도 사용하지만, 완전히 같지 않을 수도 있다.

## stringify()

구분 데이터(값)
object JSON 오브젝트
파라미터 변환 대상
함수 또는 배열opt
가독성을 위한 구분자opt
반환 반환 결과
  • JS 타입을 JSON 타입의 문자열로 변환
  • JSON.stringify() 형태로 호출한다.
  • 첫 번째 파라미터
* 큰 따옴표("") 안에 작성됨
var value = {
	book: '책',
    title: 123
};
var result = JSON.stringify(value);
log(result);

=>
{"book":"첵", "title":123}

변환이란 큰 따옴표 안에 작성되도록 만든는 것을 의미!!
프로퍼티 이름인 book이 "book"으로 변환
'책'이 "책"으로 변환,
숫자는 변환하지 않는다.
* 배열 변환
var value = ['book', '책', 123];
var result = JSON.stringify(value);
log(result);

=>
["book", "책", 123]
* 특수한 값 변환
log(JSON.stringify([Infinity, NaN, null]));
log(JSPN.stringify([true, false]));

=>
[null, null, null]
[true, false]

Infinity, NaN, null은 null로 변환된다.
true, false는 변환하지 않는다.
* undefined 반환
log(JSON.stringify(undefined));
log(JSON.stringify([undefined]));
log(JSON.stringify({value: undefined}));

=>
undefined
[null]
{}

undefined는 작성한 곳에 따라 다르게 변환된다.
값 하나이면 그대로 변환
배열 안에 있으면 null로 변환
프로퍼티 값이면 프로퍼티를 제외시킨다.
(프로퍼티 이름도 없어지므로 주의해야 한다.)
  • 두 번째 파라미터
* 두 번째 파라미터에 함수 작성
var data = {book: "책", point: 55};
function replace(key, value) {
	// point 값 55를 11로 변경
    return key == "point" ? 11 : value;
};

var result = JSON.stringify(data, replace);
log(result);

=>
{"book":"책", "point":11}

함수에서 return한 값을 변환 값으로 사용한다.
값을 return하지 않거나 undefined를 return하면 최종 데이터에서 제외시킨다.
즉, 데이터를 걸러 내게 된다.
* 두 번째 파라미터에 베열 작성
var data = {book: "책", point: 11, amount: 90};
var result = JSON.stringify(data, ['book', 'amount']);

log(result);

=>
{"book":"책", "amount": 90}

배열에 프로퍼티 이름을 작성
이름이 같은 것만 result에 설정
그래서 {point: 11}은 출력되지 않는다.
  • 세 번째 파라미터
* 세 번째 파라미터에 줄 분리 작성
var data = {sports:'soccer', time: 90};
var result = JSON.stringify(data, "", '\n');

log(result);

=>
{
"sports":"soccer",
"time": 90
}

사람이 데이터를 보기 쉽게 하기 위한 것으로 줄을 분리하여 표시한다.
* 들여쓰기 숫자 작성
var data = {sports:'soccer', time: 90};
var result = JSON.stringify(data, "", 4);

log(result);

=>
{
	"sport":"soccer",
    "tome": 90
}

숫자는 들여쓰기 자릿수
숫자만큼 들여쓰기를 한다.
* 문자 앞에 삽입할 문자 작성
var data = {sports:'soccer', time: 90};
var result = JSON.stringify(data, "", "##");

log(result);

=>
{
##"sport":"soccer",
##"tome": 90
}

문자열(##)을 작성하면 데이터 앞에 ##을 표시한다.

 

# JSON 타입 파싱

## parse()

구분 데이터(값)
object JSON 오브젝트
파라미터 변환 대상
파싱 데이터로 실행할 함수obj
반환 변환 결과
  • JSON 타입을 JS 타입으로 변환한다.
  • 작성 시 주의사항
var value = "123";
try {
	var result = JSON.parse(value);
} catch (e) {
	console.log("JSON 파싱  에러");
};

log(result);
log(typeof result);

=>
123
number

JSON 형태는 기본적으로 큰 따옴표 사용
즉, string 타입이지만 JSON.parse()에서 JS 타입에 맞도록 변환
"123" 은 문자열이지만 값이 숫자이므로 Number 타입의 숫자로 변환
파싱 대상이 서버에서 받은 데이터일 때, try~catch 사용은 필수 
var value = "true";
var result = JSON.parse(value);
log(result);
log(typeof result);

=>
true
boolean

"true"도 숫자처럼 true로 변환
try~catch 사용은 필수
var value = '["ABC", "가나", "12"]';
var result = JSON.parse(value);
log(result);

=>
["ABC", "가나", "12"]

배열에 작성된 String 타입의 숫자는 숫자로 변환하지 않는다.
var Object = '{"point": "123"}';
var result = JSON.parse(value);

log(result);

=>
{point: "123"}

JS는 프로퍼티 이름에 큰 따옴표를 사용하지 않으므로 큰 따옴표가 표시되지 않는다.
"123."을 "123.0"으로 작성
"0123"처럼 첫 자리에 0 사용 불가
대문자 "NULL" 사용 불가, "null" 사용
10진수 사용
  • 두 번째 파라미터 작성
var data = '{"book": "책", "movie": "영화"}';
var check = function(key, value) {
	return key === "book" ? "JS책" : value;
};

var result = JSON.parse(data, check);

log(result);

=>
{book: "JS책", movie: "영화"}

프로퍼티 이름이 "book"이면 "책"을 "JS책"으로 바꾼다.

첫 번째 파라미터의 JSON 문자열을 파싱하면, {book: "책", movie: "영화"} 형태가 된다.
파싱한 오브젝트를 하나씩 읽어가면서 두 번째 파라미터의 함수를 실행
함수에서 값을 반환하면 파싱 결과인 result 변수에 반영
값을 반환하지 않거나 undefined를 반환하면 프로퍼티가 제외되므로 반환해야 한다.

 

# Date 오브젝트

## Date 오브젝트의 개요

  • 년월일, 시분초, 밀리초를 제공한다. 이것을 시간 값(Time Value)라고 부른다.
  • 시간 값은 UTC (Universal Time Coordinated) 기준이다.
* UTC
1970년 1월 1일 기준으로 밀리초를 제공, 남는 초는 무시한다.
1970년 1월 1일 0시 기준으로 전후 100,000,000일 지원
  • UTC, GMT (Greewich Mean Time) 중 JS는 UTC 기준이다.

## 시간 값 표시 기준

  • 월은 0부터 시작
0: 1월
1: 2월
11: 12월
  • 일은 1에서 31일까지 정수로 표시
  • 요일은 0부터 시작
0: 일요일
1: 월요일
6: 토요일
var obj = new Date(1970, 1, 1, 1, 1, 1, 1);

log(obj.toLocaleString());

=>
1970. 2. 1. 오전 1:01:01

년월일, 시분초, 밀리초
두 번째가 월이며 1은 2월

 ## 시간의 문자열 형태

형태 개요
  2015-11-30T09:12:34.123
YYYY 그레고리력으로 0000 ~ 9999년의 10진수
- 하이픈
MM 월, 00에서 11까지
- 하이픈
DD 일, 01에서 31까지
T 시간을 나타내는 문자
HH 시, 오전 0시부터 경과 시간, 00에서 24까지 두 자리로 1시간 단위 값
: 콜론
mm 분, 00에서 59로 표시
: 콜론
ss 초, 00에서 59로 표시
. 초와 밀리초 구분
sss 밀리초, 3자리로 표시
Z 타임존 + 또는 - 로 연결하고 HH:mm 형태로도 표시

 

# Date 오브젝트 프로퍼티

## 프로퍼티 리스트

이름 개요
new Date() 인스턴스 생성
Date 함수
Date() 현재 시각 반환
Date.parse() 문자열 값을 밀리초로 변환
Date.UTC() UTC 기준 밀리초로 변환
Date.now() 현재 시각을 밀리초로 반환
Date.prototype
constructor 생성자
toString() 일자와 시간을 변환해서 문자열로 반환
toUTCString() UTC 일자와 시간 반환
toISOString() "ISO 8601 확장 형식의 간소화 버전" 형태로 일자와 시간 반환
toDateString() 연월일과 요일을 사람이 읽기 쉬운 형태로 반환
toTimeString() 시분초와 타임존을 사람이 읽기 쉬운 형태로 반환
toLocaleString() 일자와 시간을 지역 언어로 반환
toLocaleDateString() 연월일을 지역 언어로 반환
toLocaleTimeString() 시분초와 오전/오후를 지역 언어로 반환
toJSON() JSON.stringify()와 연동하여 JSON 형태의 일자, 시간 설정
이름 개요 이름 개요
Date.prototype
getTime() 시간값 반환 valueOf() 프리미티브 시간값 반환
getFullYear() 연도 반환 getYear() 세기 구분과 연도 2자리 반환 getFullYear() 사용 권장
getMonth() 월 반환 getDate() 일 반환
getDay() 요일 반환 getHours() 시 반환
getMinutes() 분 반환 getTimezoneOffset() UTC와 지역 시간 차이를 분으로 반환
getSeconds() 초 반환 getMilliseconds() 밀리초 반환
getUTCFullYear() UTC 연도 반환 geUTCMonth() UTC 월 반환
getUTCDate() UTC 일 반환 getUTCDay() UTC 요일 반환
getUTCHours() UTC 시 반환 getUTCMinutes() UTC 분 반환
getUTCSeconds() UTC 초 반환 getUTCMilliseconds() UTC 밀리초 반환
이름 개요 이름 개요
Date.prototype
setFullYear() 연도 변경, 월, 일 변경 가능 setYear() 두 자리로 연도 변경, setFullYear() 사용 권장
setMonth() 월 변경, 일 변경가능 setDate() 일 변경
setHours() 시 변경 setMinutes() 분 변경, 초, 밀리초 변경 가능
setSeconds() 초 변경, 밀리초 변경 setMilliseconds() 밀리초 변경
setTime() 1970년 1월 1일부터 경과한 밀리초 변경 setUTCFullYear() UTC 연도 변경, 월, 일 변경 가능
setUTCMonth() UTC 월 변경, 일 변경 가능 setUTCDate() UTC 일 변경
setUTCHours() UTC 시 변경 setUTCMinutes() UTC 기준 분 변경, 초, 밀리초 변경 가능
setUTCSeconds() UTC 초 변경, 밀리초 변경 가능 setUTCMilliseconds() UTC 밀리초 변경

## new Date()

구분 데이터(값)
파라미터 년, 월, 일[, 시[, 분[, 초 [, 밀리초]]]]
반환 생성한 Date 인스턴스
  • Date 인스턴스를 생성, 파라미터 값을 인스턴스의 프리미티 값으로 설정한다.
var obj = new Date(2019, 02);

log(obj);

=>
Fri Mar 01 2019 00:00:00 GMT+0900 (한국 표준시)

값을 작성하지 않으면 0으로 간주
  • 파라미터를 작성하지 않으면 UTC 기준 현재 시간
log(new Date());

=>
Sun Jul 28 2019 04:40:45 GMT+0900 (한국 표준시)
  • 파라미터를 문자열로 작성하면 "2019-12-15T09:11:23:123" 밀리초로 변환
log(new Date("2019-02");

=>
Fir Feb 01 2019 09:00:00 GMT+0900 (한국 표준시)

문자열 작성의 차이
new Date(2019, 02)에서 02가 3월로 변환되지만,
new Date("2019-02")는 2월로 변환된다.
  • 시간 자동 넘김
log(new Date(2019 11, 34));

=>
Fri Jan 03 2020 00:00:00 GMT+0900 (한국 표준시)

월일시분초 범위를 넘치면 상위 시간값에 반영된다.
new Date(2019, 11, 34)에서
34가 31을 넘치므로 3을 남겨두고 11에 1을 더한다.
12는 13월이므로 1을 남겨두고 2019에 1을 더한다.
그래서 2020.01.03이 출력

## Date.now()

구분 데이터(값)
object Date 오브젝트
파라미터 사용하지 않음
반환 밀리초
  • 현재 시간을 밀리초로 반환
log(Date.now());
log(new Date());

=>
1564256664202
SUn Jul 28 2019	04:44:24 GMT+0900 (한국 표준시)

## Date.parse()

구분 데이터(값)
object Date 오브젝트
파라미터 년월일, 시분초, 밀리초
반환 밀리초
  • 문자열 값을 밀리초로 변환, 1970. 01. 01 부터 경과한 시간
log(Date.parse("2019-01-23T09:11:23.123"));

=>
1548202283123

## Date 오브젝트 함수 분류

  • 시간을 반환하는 함수 : getMonth(), getDate() 등
var obj = new Date(2019, 02, 15);
log(obj.getMonth());
log(obj.getDate());

=>
2
15
  • 시간을 설정하는 함수 : setMonth(), setDate() 등
var obj = new Date();
log(obj.valueOf());
log(obj.setMonth(01));
log(obj.setDate(15));

=>
1564256773401
1551296773401
1550173573401
  • 함수 이름으로 기능을 알 수 있으므로 설명 생략
  • 주의사항
클라이언트의 시간은 사용자가 변경 가능

마감 시간처럼 시간이 중요할 때에는 '서버 시간'을 사용

 

# Math 오브젝트

## Math 오브젝트 개요

  • 수학 계산용 오브젝트로 상수, 절댓값, 사인, 탄젠트 등을 제공
  • new 연산자로 인스턴스 생성 불가 : Math, JSON, 글로벌 오브젝트
  • Math 오브젝트에 있는것은 메소드가 아니라 함수이다.
Math.abs(값) 형테

 ## Math 상수

이름 개요
Math 상수  
E 자연로그 상수(e)
LN10 자연로그 10
LN2 자연로그 2
LOG2E 밑이 2인 e(자연로그 밑) 로그
LOG10E e의 사용 로그(10을 밑으로 하는 로그)값
PI 파이
SQRT1_2 0.5의 제곱근 값
SORT2 2의 제곱근

## Math 함수

이름 개요
abs() 절댓값 반환
floor() 소수 이하 버림, 정숫값 반환
ceil() 소수 이하 올림, 정숫값 반환
round() 소수 이하 반올림, 정숫값 반환
max() 최댓값
min() 최솟값
random() 0에서 1미만 난수
pow() x의 y자승 값
cos() 코사인(cosine)
acos() 아크 코사인(arc cosine)
sin() 사인(sine)
asin() 아크 사인(arc sine)
atan() 아크 탄젠트(arc tangent)
atan2() x, y 좌표의 아크 탄젠트(arc tangent)
sqrt() 제곱근
exp() 자연로그 상수(e)의 제곱근
log() 자연로그 값
  • ES6에서는 수학 함수가 많이 늘어났다. 이로 인해 자바스크립트의 사용 범위가 더 넓어졌고, 특히 머신러닝과 딥러닝에서 Math 함수는 유용하다.

## abs()

구분 데이터(값)
파라미터
반환 변환한 절댓값
  • 음수를 양수로 변환한 절댓값 반환
log(Math.abs(-123));
log(Math.abs(-Infinity));

=>
123
Infinity

## floor()

구분 데이터(값)
파라미터
반환 변환한 값
  • 소수 이하 버림, 정숫값 반환
  • 소수 이하 값이 있으면서 음수이면 -1을 더해 반환한다.
log(Math.floor(5.3));
log(Math.floor(-1.7));
log(Math.floor(-1.0));

=>
5
-2
-1

## round()

구분 데이터(값)
파라미터
반환 변환한 값
  • 소수 이하 반올림, 정숫값 반환
  • 양수이면 반올림, 음수이면 반내림
log(Math.round(5.1));
log(Math.round(5.5));
log(Math.round(-1.6));
log(Math.round(-1.3));

=>
5
6
-2
-1

## max()

구분 데이터(값)
파라미터 콤마로 구분하여 값 작성
반환 변환한 값
  • 파라미터 값 중에서 가장 큰 값을 반환한다
  • 파라미터 값을 전부 숫자로 변환하여 비교한다. 
  • NaN가 하나라도 있으면 NaN 반환
log(Math.max(5, 3, 9));
log(Math.max(5, 3, "AB"));

=>
9
NaN

## min()

구분 데이터(값)
파라미터 콤마로 구분하여 값 작성
반환 변환한 값
  • 파라미터 값 중에서 가장 작은 값을 반환한다.
  • 파라미터 값을 전부 숫자로 변환하여 비교한다.
  • NaN가 하나라도 있으면 NaN 반환
log(Math.min(5, 3, 9));
log(Math.min(5, 3, "AB"));

=>
3
NaN

## pow()

구분 데이터(값)
파라미터 x
y
반환 변환한 값
  • 파라미터 x 값의 y 승 값을 반환
  • y가 0 일 때 x가 NaN라도 1을 반환
  • y가 NaN 이면 NaN 반환
  • 이처럼 경우의 수가 많으므로 사용하기 전 테스트가 필요하다.
log(Math.pow(10, 20));
log(Math.pow(10, 0));
log(Math.pow("A", 1));
log(Math.pow(1, "A"));
log(Math.pow(1));

=>
100
1
NaN
NaN
NaN

두 번째 파라미터를 작성하지 않으면 NaN

## random()

구분 데이터(값)
파라미터 사용하지 않음
반환 변환한 값
  • 0에서 1미만 사이의 난수를 반환한다.
log(Math.random());
log(Math.random());

=>
0.5841552285452716
0.6775928199911607

random() 함수를 실행할 때마다 다른 값이 반환된다.
반응형
반응형

# 자바스크립트 학습

# Object 오브젝트 (ES5)

## ES5 Object의 특징

  • 함수가 추가 (추가된 메소드는 없음)
  • 빌트인 Object의 모든 메소드는 대부분 빌트인 오브젝트에 첨부됨
  • 빌트인으로 오브젝트를 생성하므로 연결이 많이 발생한다.
  • 함수는 첨부되지 않으므로 연결 부하를 줄일 수 있다.

 ## ES5 Object 함수

이름 개요
defineProperty() 프로퍼티 추가, 프로퍼티 속성 변경
defineProperties() 다수의 프로퍼티 추가, 속성 변경
getPrototypeOf() prototype에 연결된 프로퍼티 반환
getOwnPropertyNames() 프로퍼티 이름을 배열로 반환
keys() 열거 가능 프로퍼티 이름 반환
getOwnPropertyDescriptor() 디스크립터 속성 반환
preventExtensions() 프로퍼티 추가 금지 설정
isExtensible() 프로퍼티 추가 금지 여부 반환
seal() 프로퍼티 추가, 삭제 금지 설정
isSealed() 프로퍼티 추가, 삭제 금지 여부 반환
freeze() 프로퍼티 추가, 삭제/변경 금지 설정
isFrozen() 프로퍼티 추가, 삭제/변경 금지 여부 반환

## 프로퍼티 디스크립터

이름 개요
value [[Value]], 설정할 값
writable [[Writable]], 값 변경 가능 여부
get [[Get]], 값 반환 프로퍼티 함수
set [[Set]], 값 설정 프로퍼티 함수
enumerable [[Enumerable]], 프로퍼티 열거 가능 여부
configurable [[Configurable]], 프로퍼티 삭제 가능 여부

 

# Object 프로퍼티 추가

## defineProperty()

구분 데이터(값)
object Object 오브젝트
파라미터 대상 오브젝트
프로퍼티 이름
속성
반환 대상 오브젝트
  • 대상 오브젝트에 프로퍼티 추가 또는 프로퍼티 속성 변경
  • 프로퍼티마다 상태를 갖고 있다.
상태란?
변경/삭제/열거 가능 여부
상태가 가능일 때만 처리 할 수 있다.
프로퍼티를 추가할 때 상태를 결정한다.
var obj = {};
Object.defineProperty(obj, "book", {
	value: "JS북",
    enumerable: true
});

log(obj);

=>
{book: JS북}

첫 번째 파라미터에 프로퍼티를 추가할 오브젝트(obj) 작성
두 번째 파라미터에 프로퍼티 이름(book)를 작성
세 번째 파람티ㅓ {value: "JS책"} 에서 value는 프로퍼티 값을 나타내는 속성,
"JS책"은 value의 속성 값으로 [실행 결과]처럼 프로퍼티 값이 된다.

## defineProperties()

구분 데이터(값)
object Object 오브젝트
파라미터 대상 오브젝트
프로퍼티 이름과 속성
반환 대상 오브젝트
  • 다수의 프로퍼티를 추가하거나 속성을 변경한다.
  • 함수 기능은 defineProperty()와 같다.
var obj = {};
Object.defineProperties(obj, {
	soccer: {
    	value: "축구", enumerable: true
    },
    basketball: {
    	value: "농구", enumerable: true
    }
});

for (var name in obj) {
	log(name + ":" + obj[name]);
};

=>
soccer: 축구
basketball: 농구

 

# 프로퍼티 디스크립터

## 프로퍼티 디스크립터

타입 속성 이름 속성 값 디폴트 값 개요
데이터 value JS 지원 데이터 타입 undefined 프로퍼티 값으로 사용
writable true, false false false: value 값 변경 불가
악세스 get Function Object, undefined undefined 프로퍼티 함수
set Function Object, undefined undefined 프로퍼티 함수
공용 enumerable true, false false false: for-in으로 열거 불가
configurable true, false false false 프로퍼티 삭제 불가:
  • 프로퍼티 디스크립터(Descriptor) : 프로퍼티의 속성 이름과 속성 값을 정의한다.
  • 디스크립터 타입 분류
데이터 프로퍼티 디스크립터
악세스(Access) 프로퍼티 디스크립터
공용 프로퍼티 디스크립터
디스크립터 타입에 속한 속성만 같이 사용할 수 있다.

## 디스크립터 타입 인식 기준

타입 속성 이름
데이터 value
writable
악세스 get
set
고용 enumerable
configurable
  • 먼저 value 또는 writable 작성 체크
  • 작성되어 있으면 데이터 프로퍼티 디스크립터 타입으로 인식
  • 작성되어 있지 않으면 악세스 프로퍼티 디스크립터 타입으로 인식
  • 데이터와 악세스 프로퍼티 디스크립터를 함께 작성할 수 없으므로 구분 가능

## value 속성

  • 프로퍼티 값을 {value: "JS북"} 형태로 작성, for~in에서 "JS북"이 프로퍼티 값이 된다.
var obj = {};
Object.defineProperty(obj, "book", {
	value: "JS북",
    enumerable: true
});

for (var name in obj) {
	log(name);
    log(obj[name]);
};

=>
book
JS북
  • value 속성을 get/set 속성과 같이 작성 불가능하다.
var obj = {};
Object.defineProperty(obj, "book", {
	value: "JS북",
    // get: function() {}
});

## writable

  • 프로퍼티 값 변경 가능, 불가
  • writable : true 의 경우 프로퍼티 변경 가능
var obj = {};
Object.defineProperty(obj, "book", {
	value: "JS책",
    // 변경 가능
    writable: true
});
obj.book = "변경 가능";
log(obj.book);

=>
변경 가능
  • writable : false 의 경우 프로퍼티 변경 불가, 에러가 발생하지 않지만 값이 변경되지 않는다.
var obj = {};
Object.defineProperty(obj, "book", {
	value: "JS책",
    // 변경 불가
    writable: false
});
obj.book = "변경 불가";
log(obj.book);

=>
JS책

## enumerable 속성

  • for~in으로 열거 가능 여부를 설정
  • enumerable: true의 경우 프로퍼티 열거 가능
var obj = {};
Object.defineProperty(obj, "book", {
	value: "JS북",
    // 열거 가능
    enumerable: true
});

for (var name in obj) {
	log(name, ":" + obj[name];
};

=>
book:JS북
  • enumerable: false의 경우 프로퍼티 열거 불가, 디폴트 값: false
var obj = {};
Object.defineProperty(obj, "book", {
	value: "JS북",
    // 열거 불가
    enumerable: false
});

for (var name in obj) {
	log(name, ":" + obj[name];
};

=>

## configurable

  • 프로퍼티 삭제 가능, 불가능 여부
  • configurable: true인 경우 프로퍼티 삭제 가능, value 이외 속성 변경 가능
var obj = {};
Object.defineProperty(obj, "book", {
	value: "JS북",
    //삭제 가능
	configurable: true
});

delete obj.book;
log(obj.book);

=>
undefined
  • configurable: false인 경우 프로퍼티 삭제 불가, value 이외 속성 변경 불가, 디폴트 값: false
var obj = {};
Object.defineProperty(obj, "book", {
	value: "JS북",
    //삭제 불가
	configurable: true
});

delete obj.book;
log(obj.book);

=>
JS북

 

# getter, setter

## get 속성

  • getter는 OOP의 용어로 객체지향 프로그래밍을 지원하는 언어에서 사용 가능하다.
  • 프로퍼티로 값을 구할 때 value 속성이 없으면 get 속성을 호출
var obj = {};
Object.defineProperty(obj, "book", {
	get: function() {
    	return "JS책";
    }
});

var result = obj.book;
log(result);

=>
JS책

var result = obj.book; 코드를 만나면
obj.book의 get 함수가 호출되며 get 함수에서 "JS책"을 반환
반환된 "JS책"을 result 변수에 할당
  • obj.book.get() 처럼 함수로 호출하면 에러 발생

## set 속성

  • setter는 OOP의 용어로 객체지향 프로그래밍을 지원하는 언어에서 사용 가능하다.
var obj = {};
Object.defineProperty(obj, "book", {
	set: function(param) {
    	data.title = param;
    },
    get: function() {
    	return data.title;
    }
});

obj.book = "JS책";
log(obj.book);

=>
JS책

obj.book = "JS책"; 코드를 만나면
obj.book의 set 함수를 호출하면서 "JS책"을 파라미터 값으로 넘겨 준다.
data의 title 프로퍼티에 "JS책"을 설정

obj.book; 코드를 만나면
obj.book의 get 함수가 호출되며 get 함수에서 data.title 값을 반환
setter에서 설정한 "JS책"이 반환된다.

 

# 프로퍼티 추출

## getPrototypeOf()

구분 데이터(값)
object Object 오브젝트
파라미터 대상 인스턴스
반환 프로퍼티
  • 파라미터의 prototype에 연결된 프로퍼티를 반환
function Book(point) {
	this.point = point;
};

Book.prototype.getPoint = function() {};
Book.prototype.setPoint = function() {};
var obj = new Book(100);

var result = Object.getPrototypeOf(obj);
for (var key in result) {
	log(key + ":" + result[key]);
};

=>
getPoint: function() {}
getPoint: function() {}

파라미터에 인스턴스를 작성
인스턴스를 생성한 function 오브젝트의 prototype에 연결된 프로퍼티를 반환
생성자 함수인 Book을 작성하거나 Book.prototype을 작성하면 반환하지 않는다.
this.point는 prototype에 연결되어 있지 않으므로 반환되지 않는다.
  • setPrototypeOf()가 ES5 스펙에는 없고 ES6에 있다.

## getOwnPropertyNames()

구분 데이터(값)
object Object 오브젝트
파라미터 대상 오브젝트
반환 [프로퍼티 이름]
  • 오브젝트의 프로퍼티 이름을 배열로 반환
  • 열거 가능 여부를 체크하지 않는다.
  • 자신이 만든 프로퍼티가 대상, 다른 오브젝트에서 받은 프로퍼티는 제외한다.
var obj = {};
Object.defineProperties(obj, {
	book: {value: "책"},
    point: {value: 123}
});

var name = Object.getOwnPropertyNames(obj);

for (var k = 0; k < name.length; k++) {

	log(names[k]);
};

=>
book
point

## keys()

구분 데이터(값)
object Object 오브젝트
파라미터 대상 오브젝트
반환 프로퍼티 이름
  • 열거 가능 프로퍼티 이름을 반환 (enumerable: true)
var obj = {};
Object.defineProperties(obj, {
	book: {
    	value: "책", enumerable: true
    },
    point: {value: 123}
}):

var names = Object.keys(obj);

for (var k = 0; k < names.length; k++) {
	log(names[k]);
};

=>
book

 

#  프로퍼티 디스크립터 함수

## getOwnPropertyDescriptor()

구분 데이터(값)
object Object 오브젝트
파라미터 대상 오브젝트
프로퍼티 이름
반환 디스크립터
  • 프로퍼티 디스크립터의 속성 이름, 값을 반환한다.
  • 다른 오브젝트에서 받은 프로퍼티는 제외 
var obj = {};
Object.defineProperty(obj, "book", {
	value: "책",
    writable: true, enumerable: true
});

var desc = 
Object.getOwnPropertyDescriptor(obj, "book");

for (var key in desc) {
	log(key + ":" + desc[key]);
};

=>
value: 책
writable: true
enumerable: true
configurable: false

## preventExtensions()

구분 데이터(값)
object Object 오브젝트
파라미터 대상 오브젝트
반환 대상 오브젝트
  • 오브젝트에 프로퍼티 추가 금지 설정
  • 프로퍼티 삭제, 변경은 가능
  • 추가 금지를 설정한 후에는 추가 가능으로 변경 불가
var obj = {};
Object.preventExtensions(obj);
try {
	Object.defineProperty(obj, "book", {
    	value: "책"
    });
} catch (e) {
	log("추가 불가");
};

=>
추가 불가

추가 금지 상태에서 프로퍼티를 추가하면 에러 발생

## isExtensible()

구분 데이터(값)
object Object 오브젝트
파라미터 대상 오브젝트
반환 true / false
  • 오브젝트에 프로퍼티 추가 금지 여부 반환
  • true일 경우 추가 가능, false일 경우 추가 불가
var obj = {};
Object.defineProperty(obj, "book", {
	value: "책",
});

log(Object.isExtensible(obj));

Object.preventExtension(obj);

log(Object.isExtensible(obj));

=>
true
false

## seal()

구분 데이터(값)
object Object 오브젝트
파라미터 대상 오브젝트
반환 대상 오브젝트
  • 오브젝트에 프로퍼티 추가, 삭제 금지 설정
  • 추가 금지는 오브젝트 단위로 설정하고, 삭제 금지는 프로퍼티 단위로 설정한다.
  • 추가 금지를 하더라도 변경은 가능하다.
var obj = {};
Object.defineProperty(objm "book", {
	value: "책", writable: true
});

Object.seal(obj);
try {
	Object.defineProperty(obj, "sports", {
    	value: "스포츠"
    });
} catch (e) {
	log("추가 불가");
};

=>
추가 불가

## isSealed()

구분 데이터(값)
object Object 오브젝트
파라미터 대상 오브젝트
반환 true / false
  • 오브젝트에 파라미터 추가, 삭제 금지 여부 반환
  • trus일 경우 불가, false일 경우 가능
var obj = {};
Object.defineProperty(obj, "book", {
	value: "책", writable: true
});

log(Object.isSealed(obj));

Object.seal(obj);
log(Object.isSealed(obj));

=>
false
true

## freeze()

구분 데이터(값)
object Object 오브젝트
파라미터 대상 오브젝트
반환 대상 오브젝트
  • 오브젝트에 프로퍼티 추가, 삭제, 변경 금지 설정
var obj = {};
Object.defineProperty(obj, "book", {
	value: "JS책", writable: true
});

Object.freeze(obj);
try {
	Object.defineProperty(obj, "book", {
    	value: "포인트"
    });
} catch (e) {
	log("변경 불가");
};

log(obj.book);

=>
변경 불가
JS책

## isFrozen()

구분 데이터(값)
object Object 오브젝트
파라미터 대상 오브젝트
반환 true / false
  • 오브젝트에 프로퍼티 추가, 삭제, 변경 금지 여부 반환
  • true일 경우 불가, false일 경우 가능
var obj = {};
Object.defineProperty(obj, "book", {
	value = "책",
    writable: true
});

log(Object.isFrozen(obj));

Object.freeze(obj);
log(Object.isFrozen(obj));

=>
false
true
반응형
반응형

# 자바스크립트 학습

# 자바스크립트 특징

## 자바스크립트의 특징

  • 자바스크립트는 스크립팅(Scripting) 언어이다.
  • 스크립팅 언어의 특징
소스 파일의 코드를 사전에 컴파일하여 실행 파일을 만들지 놓지 않고,
사용하는 시점에 컴파일하고 실행한다.
var value = 123;

var book = function() {
	var point = 456;
    var getPoint = function() {
    	return point;
    };
    
    getPoint();
};

book();
  •  컴파일 순서
소스 파일의 위에서부터 아래로 컴파일.

function 키워드를 만나면 function 오브젝트를 생성.

이때, 함수 안의 코드는 컴파일하지 않음, 함수가 호출되었을 때, 위의 방법으로 컴파일한다.

## 자바스크립트의 객체지향

  • 객체 지향 프로그래밍 언어 (OOP : Object Oriented Programming)
  • 자바스크립트는 객체 지향 언어이다.
  • 자바스크립트 OOP 구현
다른 언어의 OOP 구현과 차이가 잇으므로 비교하는 것은 의미 없다.
자바스크립트 특징이 반영된 OOP 구현

## OOP와 객체

  • 객체(Object)
개념적 접근
행위(Behavior)와 속성(Attribute)으로 구성
행위 : 먹다, 마시다 등
속성 : 밥, 사이다 
  • 객체를 형상화 하면
행위가 메소드가 되고,
속성이 프로퍼티가 된다.
객체가 클래스가 된다.
  • 클래스(Class)
행위와 속성을 정의한 것으로 인스턴스로 생성하여 프로그램에서 사용한다.

 

# 자바스크립트 객체 형태

## 자바스크립트 객체 형태

  • Object 오브젝트 형태
var book = {
	read: function(param){코드}
};
  • 오브젝트 형태는 인스턴스를 생성할 수 없다.
  • Function 오브젝트 형태
function readBook(param){코드};

객체이지만, OOP의 객체라고 하기에는 부족하다.
객체에 메소드가 하나 있는 모습.

## prototype

  • 자바스크립트의 OOP 구현 방법
prototype에 메소드를 연결한다.
  • prototype에 연결하여 작성한다.
var Book = function(){};

Book.prototype.getBook = function() {
	return "JS북";
};

=>
Book 함수 작성
Book.prototype에 getBook 메소드 연결
prototype이 오브젝트이므로
{이름: 메소드} 형태로 연결할 수 있다.
  • 다른 언어는 class 안에 메소드와 프로퍼티를 작성하지만, 자바스크립트는 prototype에 메소드를 연결하여 작성한다.
  • ES6에서 class에 메소드를 작성한다. 하지만 자바스크립트 내부에서는 prototype에 연결한다.
class Book {
	constructor(title) {
    	this.title = title;
    }
    getBook() {
    	return this.title;
    }
};

 

# 자바스크립트 인스턴스

## 인스턴스

  • Instance란 Class를 new 연산자로 생성한 것이다.
  • 인스턴스의 목적
Class에 작성된 메소드를 사용하는 것.
인스턴스마다 프로퍼티 값을 유지하는 것.

예)
축구 경기에서
각 팀 적용되는 규칙은 같지만(메소드),
팀마다 점수는 다르다.(프로퍼티 값)

## new 연산자

구분 데이터(값)
constructor 생성자
파라미터 값opt
반환 생성한 인스턴스
  • 인스턴스를 생성하여 반환한다.
new Book("JS책");

인스턴스를 생성하므로 Book()을 생성자 함수라고 부른다.

코딩 관례로 첫 문자를 대문자로 작성한다.
  • 파라미터의 경우 생성자 함수로 넘겨 줄 값을 소괄호()에 작성한다.
var Book = function(point) {
	this.point = point;
};

Book.prototype.getPoint = function() {
	return this.point + 100;
};

var oneInstance = new Book(200);

log(oneInstance.getPoint());

=>
300

new Book(200)으로 인스턴스 생성
oneInstance가 인스턴스 이용
파라미터 값을 생성하는 인스턴스에 설정
따라서 인스턴스마다 고유의 값을 가질 수 있음
oneInstance.getPoint() 형태로 prototype에 연결된 getPoint() 메소드 호출

## Instanceof

  • 오브젝트로 생성한 인스턴스 여부를 반환
  • instance가 object로 생성한 인스턴스이면 true, 아니면 false 반환
instance instatnceof object

instance 위치에 인스턴스 작성.
object 위치에 비교 기준 오브젝트 작성.
var Book = function(point) {
	this.point = point;
};

Book.prototype.getPoint = function() {
	return this.point + 100;
};

var oneInstance = new Book(200);

log(oneInstance instanceof Book);

=>
true

new Book()으로 인스턴스 생성
oneInstance instanceof Book
oneInstance가 Book()으로 생성한 인스턴스이므로 true 반환

 

# 메소드 형태

## 메소드 호출 형태

  • 데이터 형태가 아래처럼 배열로 만들어져 있을 때
배열 : ["book", "책", 123]
  • OOP의 일반적인 형태는 아래와 같다.
var data = ["book", "책", 123];

var obj = new Array();

var result = obj.concat(data);

log(result);

=>
[book, 책, 123]

new Array()로 인스턴스 생성.
obj.concat(data);
obj 인스턴스의 concat() 호출
데이터를 파라미터로 넘겨 준다.
자바스크립트는 일반적으로 이 방법을 사용하지 않는다.
  • 자바스크립트의 형태1 - 데이터로 메소드 호출
var data = ["book", "책", 123];
var result = data.concat();
log(result);

=>
[book, 책, 123]

data.concat();
엔진이 data 타입에 따라 오브젝트 결정
data가 배열이므로 Array의 concat() 호출 data를 파라미터로 넘겨 준다.
  • 자바스크립트 형태2 - 오브젝트의 함수 호출
var data = ["Book", "책", 123];
var bookObj = {
	concat: function(data) {
    	return data.concat();
    }
};
log(bookObj.concat(data));

=>
[book, 책, 123]
  • 자바스크립트 형태3 - 인스턴스의 메소드 호출
var data = ["Book", "책", 123];
var Book = function(data) {
	this.data = data;
};
Book.prototype.concat = function() {
	return this.data.concat();
};

var oneInstance = new Book(data);

log(oneInstance.concat());

=>
[book, 책, 123]
  • Object 확장
네임스페이스로 사용.
Book = {};
Book.Javascript = {}; Book.Html = {};

## 메소드 사용 팁

  • 메소드를 알 수 없을 때
MDN을 활용한다.

MDN에서 "오브젝트 이름" 으로 검색한다.

왼쪽의 리스트에서 메소드 이름을 찾는다.

메소드 이름이 시맨틱을갖고 있으므로, 메소드 이름으로 기능 예측이 가능하다.
  • 메소드를 알고 있을 때
기능을 정확하기 사용하기 위해 사용한다.
반응형

+ Recent posts