[모던 자바스크립트 Deep Dive] - 6~9장

Lee Jeong Min·2021년 9월 7일
0
post-thumbnail

이 글은 책 모던 자바스크립트 6장 ~ 9장을 읽고 정리한 글입니다.

06장 - 데이터 타입

자바스크립트의 데이터 타입의 종류 - 원시타입 vs 객체 타입

원시

  • 숫자 타입
  • 문자열 타입
  • 불리언 타입
  • undefined 타입
  • null 타입
  • 심벌 타입

객체

  • 객체
  • 함수
  • 배열

숫자 타입

자바스크립트는 하나의 숫자 타입만 존재(int, long, float 등이 없음)

--> 모든 수를 실수로 처리한다.

배정밀도 64비트 부동소수점 형식

출처: 부동 소수점 wiki

숫자를 표현할 때 부호 비트, 지수, 가수부분으로 나누어서 숫자를 표현하기 때문에 오차가 발생할 수도 있음 (실수를 정확히 표현하지는 못함)

// 숫자 타입은 모두 실수로 처리됨.
console.log(1 === 1.0); // true

숫자타입의 세 가지 특별한 값

// 숫자 타입의 세 가지 특별한 값
console.log(10 / 0); // Infinity
console.log(10 / -0); // -Infinity
console.log(1 * 'String'); // NaN

NaN 의 경우 대소문자를 구분하여 써주어야 하며 다르게 사용하였을 시, ReferenceError가 발생한다

var x = nan; // ReferenceError: nan is not defined

문자열 타입

문자열은 JS에서 원시타입으로 변경 불가능한 값이며(문자열 생성 시 문자열 변경 X)

  • ' '(작은 따옴표)
  • " "(큰 따옴표)
  • ` `(백틱)

으로 묶어서 사용한다. 이를 묶는 이유는 키워드나 식별자 같은 토큰과 구분하기 위해서이다.

템플릿 리터럴

ES6부터 도입된 문법이며 런타임에 일반 문자열로 변환되어 처리된다.

var template = `Template literal`;
console.log(template); // Template literal

템플릿 리터럴은 멀티라인 문자열, 표현식 삽입을 할 때 유용하게 사용된다.

멀티라인의 경우 기존 따옴표로 묶게 되면 이스케이프 \n과 같은 이스케이프 시퀀스를 사용해야 되는데 이를 사용하지 않고도 줄바꿈이 허용되며 모든 공백도 있는 그대로 적용된다.

표현식의 경우 ${variable} 형식으로 백 틱안에 있는 문자열 안에 변수를 넣어 주면 간단히 문자열에 넣어서 사용할 수 있다.

불리언 타입

true와 false 가 불리언 타입이다.

undefined 타입

var 키워드로 선언한 변수는 암묵적으로 undefined로 초기화된다.

--> 개발자가 의도적으로 할당하는 값이 아닌 자바스크립트 엔진이 변수를 초기화 할 때 사용하는 값이다.

변수에 값이 없다는 것을 알리고 싶을 때는 null을 사용한다.

선언과 정의

다른 언어에선 선언은 식별자의 존재를 알리는 것, 정의는 메모리 주소를 할당하는 것인데 자바스크립트는 변수를 선언하면 암묵적으로 정의가 이루어짐(var a; 라는 코드가 실행되면 undefined가 할당되는 것)

ECMASCript 사양에서 변수는 '선언한다'라고 표현하고 함수는 '정의한다'라고 표현.

null 타입

null --> 변수에 값의 없다는 것을 의도적으로 명시하는 것

이 null을 할당하는 것은 더 이상 변수가 참조하던 값을 참조하지 않고, 참조를 제거하며, 메모리 공간에 가비지 컬렉션을 수행하게 됨.

HTML의 경우, querySelector 사용 시, 조건에 부합하는 요소가 없다면 null을 반환한다.

심볼 타입

ES6에서 추가된 7번째 타입으로, 변경 불가능한 원시 타입의 값이며 다른 값과 중복 되지 않는 유일 무이한 값.

Symbol('값') // 이렇게 Symbol타입을 사용할 수 있다.

객체 타입

위에서 언급한 타입을 제외한 나머지는 모두 객체 타입이다.

JS는 객체 기반의 언어이며, 자바스크립트를 이루고 있는 거의 모든 것이 객체

데이터 타입의 필요성

  • 값 저장 시, 확보해야 하는 메모리 공간의 크기 결정
  • 값 참조 시, 읽어들어야 하는 메모리 공간 크기 결정
  • 메모리에서 읽은 값을 어떻게 해석할 지 결정

데이터 타입에 의한 메모리 공간의 확보와 참조

데이터 타입에 따라 메모리 공간을 얼마나 잡고 읽어들어야 할지 확인하기 위해(메모리 공간의 크기)

심볼 테이블

컴파일러 또는 인터프리터는 심벌 테이블 이라고 부르는 자료 구조를 통해 식별자를 키로 바인딩된 값의 메모리 주소, 데이터 타입, 스코프 등을 관리한다.

데이터 타입에 의한 값의 해석

메모리 공간안에 있는 값을 어떻게 해석할 것인지? (숫자냐? 문자냐?)

동적 타이핑

동적 타입 언어와 정적 타입 언어

정적타입: 데이터의 타입을 사전에 정의(int, char등의 키워드 이용)

동적타입: 타입을 사전에 정의하지 않음(JS의 경우, var, let, const 키워드 이용)

자바스크립트는 변수의 선언이 아닌 할당에 의해 타입이 결정(타입 추론) 된다. 또한 재할당에 의해 변수의 타입은 언제든지 동적으로 변할 수 있다. --> 동적 타이핑

var foo;

foo = 3; // 이렇게 숫자라는 값이 변수에 할당이 되는 순간, 할당된 값을 보고 데이터 타입이 결정된다.
console.log(typeof foo); // number

변수는 타입을 가지지 않고, 값은 타입을 가짐. 현재 변수에 할당되어 있는 값에 의해 변수의 타입이 동적으로 결정된다.

동적 타입 언어와 변수

동적 타입 언어의 구조적 단점은 변화하는 변수 값을 추적하기 어렵고, 값을 확인하기 전에는 타입을 확신할 수 없다. 이로 인해 유연성은 높지만 신뢰성이 떨어진다.

따라서 변수 사용 시 유의사항이 존재한다.

  • 변수는 꼭 필요한 경우에 한해 사용하자.
  • 변수의 유효범위를 최대한 좁게하여 변수 부작용을 억제하자.
  • 전역변수는 최대한 사용 X
  • 변수보다는 상수를 사용
  • 변수 이름은 변수의 목적이나 의미를 파악할 수 있도록 네이밍 하자.

코드는 오해하지 않도록 작성해야 하고, 가독성이 좋은 코드가 좋은 코드이다

"컴퓨터가 이해하는 코드는 어떤 바보도 쓸 수 있다. 하지만 훌륭한 프로그래머는 사람이 이해할 수 있는 코드를 쓴다." - 마틴 파울러


07장 - 연산자

연산자의 종류 : 산술, 할당, 비교, 논리, 타입 지수 연산

연산의 대상: 피연산자(값으로 표현될 수 있는 표현식)
연산자: 피연산자를 연산하여 새로운 값을 만드는 역할

산술 연산자

산술 연산자로 새로운 숫자 값을 만들며 불가능한경우 NaN을 반환한다.

이항 산술 연산자

2개의 피연산자 사용. 부수효과 X(피연산자의 값이 바뀌지 않고 언제나 새로운 값을 만듦)

종류: +, -, *, /, %

단항 산술 연산자

1개의 피연산자 사용. 부수효과 있는 것도 있고 없는 것도 있음

종류: ++, --(이 둘은 부수효과 O --> 피연산자를 증가시키거나 감소시킴), +, -(이 둘은 어떠한 효과도 없지만 -의 경우 양수를 음수로, 음수를 양수로 반전한 값 반환)

var x = 1;

x++;
console.log(x); // 2

이 결과에서 볼 수 있듯이, 증가/감소(++/--)연산자는 피연산자의 값을 변경하는 부수 효과가 있으며, 암묵적 할당이 이루어진다.

숫자 타입이 아닌 피연산자에 + 단항 연산자 사용시, 숫자 타입으로 변환하여 반환함(true에 + 붙잉면 +1이 생김). 이 경우, 피연산자를 변경하는 것이 아니고 타입만 변경하여 부수효과 X. - 단항 연산자의 경우 부호를 반전한 값을 생성해 반환함. 마찬가지로 부수효과 X

문자열 연결 연산자

+ 연산자는 피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작

'1' + 2 // '12'
1 + '2' // '12'

1 + true // 2

1 + false // 1

1 + null // 1

+undefined // -> NaN
1 + undefined // -> NaN

JS 엔진은 암묵적으로 불리언 타입의 값인 true를 숫자 타입인 1로 타입을 강제로 변환후 연산을 수행 --> 암묵적 타입변환, 타입 강제 변환

할당 연산자

할당 연산자는 좌항에 변수 값을 할당하기 때문에 변수 값이 변하는 부수효과 O

var x;

// 할당문 = 표현식인 문
console.log(x= 10); // 10

할당문은 표현식인 문으로, 할당된 값으로 평가된다. --> 이러한 특징으로 여러 변수에 동일한 값을 연쇄 할당 가능.

비교 연산자

동등/일치 비교 연산자

동등 비교 vs 일치 비교 --> 엄격성 정도의 차이

동등 비교(==)의 경우 피연사자 비교시 암묵적 타입 변환을 통해 타입을 일치 시킨 후 같은 값인지 비교.

일치 비교(===)의 경우 피연산자 비교시 타입도 같고 값도 같은 경우에 한하여 true를 반환한다.

예외: NaN

// NaN은 자신과 일치하지 않는 유일한 값이다.
NaN === NaN; // false

Object.is 메서드

-0 === +0; // true
Object.is(-0, +0); // false


NaN === NaN; // false
Object.is(NaN, NaN); // true

이 메서드는 동일,일치 비교 연산자와 달리 0과 NaN결과가 다른데, 예측 가능한 정확한 비교 결과를 반환한다. 나머지 그 외에는 일치 비교(===) 연산자와 동일하게 동작한다.

대소 관계 비교 연산자

피연산자의 크기를 비교하여 불리언 값을 반환한다.

삼항 조건 연산자

var x = 2;

var result = x % 2 ? '홀수' : '짝수';

console.log(result); // 짝수

첫 번째 피연산자가 true로 평가되면 두 번째 피연산자 반환, false이면 세 번째 피연산자를 반환한다.

삼항 조건 연산자 표현식은 값으로 평가할 수 있는 표현식인 문이다. 이 말은 즉, 값처럼 다른 표현식의 일부가 될 수 있어 유용하게 쓰일 수 있다. 가독성을 위해 조건에 따라 수행해야 할 문이 여러개라면, if ... else를 사용하는 것이 좋다.

논리 연산자

종류: ||(논리합), &&(논리곱), !(부정)
부수효과 X

피 연산자가 불리언 타입일 필요는 없고 이 경우, 불리언 타입으로 암묵적으로 변환되어 연산됨

!'Hi' // false

// 단축 평가
'Cat' && 'Dog'; // 'Dog'

쉼표 연산자

var a, b, c;

a = 1, b = 2, c = 3; // 콘솔에서 3을 반환한다.

그룹 연산자

()을 의미하며 연산자 우선순위가 가장 높다.

3 * (4 + 1); // 15

typeof 연산자

typeof은 피연산자의 데이터 타입을 문자열로 반환한다. (string, number, boolean, undefined, symbol, object, function 중 하나 반환. null을 반환하는 경우 없음)

값이 null타입인지 확인하고 싶을 때는 일치연산자(===)를 사용하여 확인.

선언하지 않은 식별자를 typeof으로 연산 시, ReferenceError가 발생하지 않고 undefined를 반환.

// notVariable 선언한적 X
typeof notVariable; // undefined

지수 연산자

**으로 사용하며, 지수 연산자 전에는 Math.pow를 사용하였다.

음수 제곱시 (-5) ** 2 = 25 와 같이 괄호로 묶어야 한다.

다른 산술 연산과 마찬가지로 할당문과 같이 사용할 수 있으며(num **= 2) 다른 이항 연사자들 보다 우선순위가 높다.

그 외의 연산자

연산자개요
?.옵셔널 체이닝 연산자
??null 병합 연산자
delete프로퍼티 삭제
new생성자 함수를 호출할 때 사용하여 인스턴스 생성
instanceof좌변의 객체가 우변의 생성자 함수와 연결된 인스턴스인지 판별
in프로퍼티 존재 확인

연산자의 부수 효과

다른 코드에 영향을 주는 부수효과가 있는 연산자: 할당 연산(=), 증가/감소 연산(++/--), delete 연산자.

할당과 증감 및 감소 연산자는 변수 값을 바꾸며, delete의 경우 객체의 프로퍼티를 삭제한다.(객체를 사용하는 다른 코드에 영향을 줌)

연산자 우선순위

우선순위가 가장 높은것은 () 이며 그 외에는 기억하기 어려우니 ()를 사용하여 명시적으로 우선순위를 조절하는 것을 권장!

연산자 결합 순서

연산자의 어느 쪽부터 평가를 수행할 것인지 나타내는 순서

좌항 -> 우항 : +, -, /, %, <, <=, >, >=, && ...

우항 -> 좌항 : ++, --, 할당 연산자(=, +=, -=, ...), !x ...


08장 - 제어문

제어문은 조건에 따라 블록을 실행하거나 반복 실행하지만 이는 위에서 아래로 순차적으로 진행하는 코드의 흐름을 혼란스럽게 하여 가독성을 해친다.

이러한 부분을 나중에 배울 forEach, map, filter, reduce같은 고차함수를 이용하여 해결하는데 여기서는 기초적인 조건문, 반복문에 대해서 배운다.

블록문

0개 이상의 문을 {}중괄호로 묶은 것으로, 블록문이라 하며 블록문으 끝에는 자체 종결성을 갖기 때문에 ;세미콜론을 붙이지 않는다.

조건문

JS는 if ... else와 switch의 두 가지 조건문을 제공한다.

if...else

if...else문의 else if의 경우 여러 번 사용 가능하다. 만약 코드 블록 내의 문이 하나라면 중괄호 생략이 가능하다.
대부분의 if...else문은 삼항 조건 연산자로 바꿔 쓸 수 있고, 이 삼항 조건 연산자는 값처럼 사용할 수 있어서 유용하지만 조건에 따른 실행내용이 많을 경우, 가독성을 위해 if...else문을 사용하는 것이 좋다.

switch 문

if...else 문의 조건식은 불리언 값으로 평가 되어야하지만 switch 문은 불리언 값 보다 문자열 혹은 숫자인 값인 경우가 많다.

switch (표현식) {
  case 표현식1:
    console.log(표현식1);
    break;
  case 표현식2:
    console.log(표현식2);
    break;
  default:
    console.log(마지막);
}

위와 같이 사용하며 만약 break가 없이 문이 끝날 때까지 모든 케이스를 실행한다면 이를 폴스루 라고 한다. default 에는 일반적으로 break를 생략한다.

조건이 엄청 많은 경우 if ... else문 보다 switch 문을 사용했을 대, 가독성이 더 좋다면 switch 문을 사용하는 것이 좋다.

반복문

총 for, while, do...while 문이 존재한다.

for

for는 처음 부분에 변수 선언 또는 할당문, 조건식, 증감식이 존재한다.

for (;;) { ... } 은 무한루프를 발생시킨다.

while

for 문은 반복 횟수가 명확할 때, while문은 반복 횟수가 불명확할 때 주로 사용!

do...while 문

코드 블록을 무조건 한 번 이상 실행 시킨다.

var count = 0;

do {
  console.log(count); // 0 1 2
  count++;
} while (count < 3);
  

break 문

레이블 혹은 반복문을 탈출하는데 쓰인다.

레이블, 반복문, switch 문의 코드 블록 외에 break를 사용시 SyntaxError가 발생한다.

레이블 문이란 식별자가 붙은 문으로 아래의 예시를 참고하여라

// foo라는 식별자가 붙은 레이블 블록
foo: {
  console.log(1);
  break foo; // foo 레이블 블록문을 탈출한다.
  console.log(2);
}

console.log('Done!');

레이블 문을 사용하게 되면 외부로 탈출할 때 유용하지만, 흐름이 복잡해져서 가독성이 나빠지고 오류를 발생시킬 가능성이 높아지기때문에 권장하진 않음

continue 문

반복문의 코드 블록 실행을 현 지점에서 중단하고 반복문의 증감식으로 실행 흐름을 이동시킨다.

let count = 0;

for (let i = 1; i < 10; i++) {
  if (i % 2 === 0)
    continue;
  count++;
}

console.log(count); // 5

짝수인 경우 continue로 count를 세지 않고 넘기며, 홀수인 경우에만 count를 센다.


09장 - 타입 변환 단축 평가

타입 변환이란?

개발자가 의도적으로 값의 타입을 변환시키는 것을 명시적 타입 변환 또는 타입 캐스팅 이라고 한다.

개발자가 의도 없이 표현식을 평가하는 도중 JS엔진에 의해 암묵적으로 타입이 자동변환 되는 것을 암묵적 타입 변환 또는 타입 강제 변환이라 한다.

이러한 타입 변환은 기존의 원시 값을 직접 변경하는 것이 아닌 새로운 원시 값을 생성하여 단 한 번 사용하고 버린다.
중요한 것은 코드가 어떻게 작동되는 것인지 예측하고 이해하면서 사용해야 한다는 것이다.

암묵적 타입 변환

문자열 타입으로 변환

+ 연산자는 피 연산자 중 하나 이상이 문자열 이면 문자열 연결 연산자로 동작한다.

리터럴의 표현식 삽입또한 평가 결과를 문자열 타입으로 암묵적 타입 변환한다.

숫자 타입으로 변환

-, *, /와 같은 산술 연산자는 피 연산자 중, 숫자 타입이 아닌 피연산자를 숫자타입으로 암묵적 타입 변환한다. 이 경우, 숫자 타입으로 변환할 수 없는 경우 산술 연산 수행이 불가능하여 표현식의 평가 결과가 NaN이 된다.

비교 연산자 또한 값을 비교하여 불리언 값을 만들기 때문에 피 연산자를 숫자 타입으로 암묵적 타입 변환한다.

불리언 타입으로 변환

JS엔진은 불리언 타입이 아닌 값을 Truthy 값(참으로 평가되는 값) 또는 Falsy 값(거짓으로 평가되는 값)으로 구분하는데, Truthy는 true로, Falsy는 false로 암묵적 타입 변환된다.

대표적 Falsy 값

  • false
  • undefined
  • null
  • -0, 0
  • NaN
  • ''(빈 문자열)

명시적 타입 변환

문자열 타입으로 변환

  • new 연산자 없이 String 생성자 사용(ex: String(1);)
  • Object.prototype.toString(ex: (1).toString();)
  • 문자열 연결 연산자를 이용하는 방법(ex: 1 + '';)

숫자 타입으로 변환

  • new 연산자 없이 Number 생성자 함수 사용(ex: Number('0');)
  • parseInt, parsefloat 함수 사용 --> 문자열만 숫자 타입으로 변환 가능(ex: parseInt('0');)
  • + 단항 산술 연산자 이용(ex: +'0';)
  • * 산술 연산자 이용(ex: true * 1;)

불리언 타입으로 변환

  • new 연산자 없이 Boolean 생성자 함수 사용(ex: Boolean('x');)
  • ! 부정 논리 연산자를 두 번 사용(ex: !!'x';)

단축 평가

논리 연산자를 사용한 단축 평가

단축 평가 표현식평가 결과
true || anythingtrue
false || anythinganything
true && anythinganything
false && anythingfalse

단축평가가 유용하게 사용되는 상황

  1. 객체가 가리키기를 기대하는 변수가 null 또는 undefined가 아닌지 확인하고 프로퍼티를 참조할 때 (var value = elem && elem.value)

  2. 함수 매개변수에 기본 값을 설정할 때(기본값을 설정해두면 undefined로 발생할 수 있는 에러 방지가능)

옵셔널 체이닝 연산자

?.로 사용하며 좌항의 피연산자가 null 또는 undefined인 경우 undefined를 반환하고, 그렇지 않으면 우항의 프로퍼티 참조를 함.

var elem = null;

var value1 = elem?.value;
console.log(value1); // undefined

var value2 = elem && elem.value;
console.log(value2); // null


var str = '';

// 문자열 길이를 참조하는데 좌항 피연산자가 null또는 undefined가 아니기 때문에 우항의 프로퍼티 참조

var length = str?.length;
console.log(length); // 0

null 병합 연산자

??로 사용하며 좌항의 피연산자가 null 또는 undefined인 경우 우항의 피연산자를 반환하고, 그렇지 않으면 좌항의 피연산자를 반환한다.

var helloNull = null ?? 'default string';
console.log(helloNull); // "default string"

// 만약 Falsy값인 0 이나 ''도 기본값으로 유효시, 예기치 않은 동작 발생 가능
var foo = '' || 'default string'; 
console.log(foo); // "default string"

// 좌항의 피연산자가 Falsy 값이라도 null 또는 undefined가 아니면 좌항 피연산자 반환
var foo2 = '' ?? 'default string';
console.log(foo2); // ""

수업시간에 정리한 부분

왜 컴퓨터의 메모리 셀의 최소단위가 1바이트는 8비트?

옛날에는 여러 비트의 컴퓨터가 있었는데 범용화를 위해 1바이트 메모리 셀을 8비트로 묶어서 사용하게 됨. 그러다가 아시아 권을 포함하게 되면서 더 많은 문자를 표현하기 위해 유니코드(2바이트를 사용)

JS언어의 특징

Js --> 자주 사용하는거 (반복문 안이나 함수 바디에 있는 부분을 먼저 컴파일 해놓아서 기계어로 바꾸어둠)
자주 사용안하는거는 인터프리터가 해석

컴파일도 같이 사용하지만 결국엔 실행파일을 안만들기 때문에 인터프리터 언어라고 함

컴파일러가 있는데 인터프리터가 만들어진 이유?

컴퓨터가 별로 안좋았던 이전에 컴파일을 돌려놓고 퇴근 -> 다음날 아침 컴파일러 결과 확인 --> 불편... 그렇다면 컴파일을 꼭 해야할까? 라는 의문 등장

컴파일러 --> OS 에 종속
인터프리터 --> OS 에 종속 X

런타임에서 발생될 수 있는 에러가 있는 경우에 컴파일이러는 한번 컴파일 하고 고치고 컴파일 하고 그래야 되는데 인터프리터는 1줄씩 해석하면 되어서 에러 디버깅에 좀 더 좋을 수 있다.

예전에는 스크립트 언어를 인터프리터 언어라고 했음

변수 네이밍

변수 - 명사형으로, 함수 - 동사형으로
is~, has~ 로 함수를 짓자(boolean을 판단할 때)

그 외

변수 선언은 런타임 전.
런타임 전에 실행 컨텍스트에서 관리.

표현식이 아닌문 -> 값을 생성하지 않음
평가 --> 표현식인 문에만 평가

완료값 --> 표현식이 아닌문을 사용하면 완료값으로 undefined가 온다.
표현식인 문을 사용하면 완료값으로 평가된 값이 온다.

console.log() --> 비표준이기 때문에 nodejs에서 출력해주는 내용과 브라우저에 출력해주는 내용이 다를 수 도 있음.

무한소수 --> 완전히 다 저장 x
실수 계산 시 오차 발생 --> 배정밀도 64비트 부동소수점 방식 때문에

'Ab' 라는 문자열 뒤에 점을 찍는 순간 자바스크립트는 암묵적으로 앞의 원시타입을 객체로 만들어주는데 이를 참조 객체라고 함.

백틱과 따옴표를 사용할 때 문자열만 있는 경우 굳이 백틱을 사용하기보다 따옴표를 사용하는 것이 좋다.

템플릿 리터럴에 다음과 같이 변수를 넣는 것을 ${a} <-- 표현식 삽입이라고 한다.

심벌은 리터럴로 못 만들고 Symbol()함수를 사용해야함.

Const --> 안정성 때문에 let보다 사용하는 것이 좋다.

++,-- 안티패턴 --> 뒤에 쓸때와 앞에쓸떄 의미가 다르고, 재할당이 암묵적으로 이루어져서 부수효과가 발생한다.(변수값이 바뀜)

isNaN(예전꺼)대신에 Number.isNaN(최신 문법)

Object.is 메서드

삼항 조건 연산자의 조건식의 x%2보다 x%2 === 0 이 더낫다고 생각(가독성 측면에서)
지수 연산에서 함수는 인수와 순서를 고려해야하기 떄문에 그냥 지수연산자가 더 낫고 가독성이 나은편이다.

변수 선언 -> 자바스크립트 엔진에 식별자의 존재를 알리는 것.

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글