오늘은
25. 1장 | 벨로퍼트와 함께 하는 모던 자바스크립트 남은 클립 4개를 듣고
26. 2장 | 벨로퍼트와 함께 하는 모던 자바스크립트 : 알고 있으면 유용한 JS 문법 부분을 들었다
언제 다 듣나 싶었는데 벌써 30회째 꾸준히 듣고 있다...
*
객체 생성자 : 함수를 통해서 새로운 객체를 만들고 그 안에 넣고 싶은 값 혹은 함수들을 구현
(예)
function Animal(type, name, sound) {
this.type = type;
this.name = name;
this.sound = sound;
this.say = function() {
console.log(this.sound);
};
}
const dog = new Animal('개', '멍멍이', '멍멍');
const cat = new Animal('고양이', '야옹이', '야옹');
dog.say();
cat.say();
(예시결과) 멍멍 야옹
*
객체 생성자를 사용 할 때는
함수 이름 : 대문자로 시작
새로운 객체를 만들 때 : new 키워드를 앞에 붙여주어야 함
*
프로토타입 : 같은 객체 생성자 함수를 사용하는 경우, 특정 함수 또는 값을 재사용
객체 생성자 함수 아래에 .prototype.[원하는키] = 코드를 입력하여 설정
(예)
function Animal(type, name, sound) {
this.type = type;
this.name = name;
this.sound = sound;
}
Animal.prototype.say = function() {
console.log(this.sound);
};
Animal.prototype.sharedValue = 1;
const dog = new Animal('개', '멍멍이', '멍멍');
const cat = new Animal('고양이', '야옹이', '야옹');
dog.say();
cat.say();
console.log(dog.sharedValue);
console.log(cat.sharedValue);
(예시결과)
멍멍
야옹
1
1
*
객체 생성자 상속받기
(예)
Cat 과 Dog 라는 새로운 객체 생성자를 만든다고 할 때
해당 객체 생성자들에서 Animal 의 기능을 재사용
function Animal(type, name, sound) {
this.type = type;
this.name = name;
this.sound = sound;
}
Animal.prototype.say = function() {
console.log(this.sound);
};
Animal.prototype.sharedValue = 1;
function Dog(name, sound) {
Animal.call(this, '개', name, sound);
}
Dog.prototype = Animal.prototype;
function Cat(name, sound) {
Animal.call(this, '고양이', name, sound);
}
Cat.prototype = Animal.prototype;
const dog = new Dog('멍멍이', '멍멍');
const cat = new Cat('야옹이', '야옹');
dog.say();
cat.say();
(예시결과) 멍멍 야옹
클래스
자바스크립트에는 없었던 기능
객체 생성자 함수를 사용하여 비슷한 작업을 구현
(예)
class Animal {
constructor(type, name, sound) {
this.type = type;
this.name = name;
this.sound = sound;
}
say() {
console.log(this.sound);
}
}
const dog = new Animal('개', '멍멍이', '멍멍');
const cat = new Animal('고양이', '야옹이', '야옹');
dog.say();
cat.say();
say 라는 함수를 클래스 내부에 선언, 메서드 라고 함, 자동으로 prototype 으로 등록
(예시결과)
멍멍
야옹
*
class 를 사용했을때에는, 다른 클래스를 쉽게 상속 할 수 있습니다.
class Animal {
constructor(type, name, sound) {
this.type = type;
this.name = name;
this.sound = sound;
}
say() {
console.log(this.sound);
}
}
class Dog extends Animal {
constructor(name, sound) {
super('개', name, sound);
}
}
class Cat extends Animal {
constructor(name, sound) {
super('고양이', name, sound);
}
}
const dog = new Dog('멍멍이', '멍멍');
const cat = new Cat('야옹이', '야옹');
dog.say();
cat.say();
(예시결과)
멍멍
야옹
*
상속을 할 때는 extends 키워드를 사용
*
삼항 연산자, ES6 문법은 아님
Condition ? true : false
조건이 true면 true, false면 false / true, false 바꿀 수도 있음
(예)
const array = [];
let text = '';
if (array.length === 0) {
text = '배열이 비어있습니다.';
} else {
text = '배열이 비어있지 않습니다.';
}
console.log(text);
(또 다른 예)
특정 조건에 따라 text 값이 달라야 하는 상황이 있다고 가정
그런 경우에는 다음과 같이 코드를 작성 할 수 있습니다.
const array = [];
let text = array.length === 0 ? '배열이 비어있습니다' : '배열이 비어있지 않습니다.';
console.log(text);
*
라인의 길이가 너묵 길어진다면 다음과 같이 작성하기도 합니다.
const array = [];
let text = array.length === 0
? '배열이 비어있습니다'
: '배열이 비어있지 않습니다.';
console.log(text);
*
중첩해서 쓸 수도 있음, 하지만 가독성이 그렇게 좋지 않음
const condition1 = false;
const condition2 = false;
const value = condition1
? '와우!'
: condition2
? 'blabla'
: 'foo';
console.log(value);
*
Truthy and Falsy
Truthy: true 같은거... Falsy: false 같은거...
(예)
function print(person) {
console.log(person.name);
}
const person = {
name: 'John'
};
print(person);
*
만약 print 함수가 다음과 같이 파라미터가 비어진 채로 실행됐다면.
function print(person) {
console.log(person.name);
}
const person = {
name: 'John'
};
print();
이 코드는 다음과 같은 에러를 생성해냅니다.
TypeError: Cannot read property 'name' of undefined
(예)
console.log(!undefined);
console.log(!null);
Falsy 한 값은 이 외에도 몇개 더 있습니다.
console.log(!undefined);
console.log(!null);
console.log(!0);
console.log(!'');
console.log(!NaN);
이 값은 모두 true 가 됩니다.
*
NaN : Not A Number ,
문자열을 숫자로 변환하는 자바스크립트 기본함수 parseInt 라는 함수를 사용하게 될 때
const num = parseInt('15', 10); // 10진수 15를 숫자로 변환하겠다는 의미
*
Falsy 한 값은 아까 나열한 다섯가지 입니다.
그리고, 그 외의 값은 모두! Truthy 한 값
*
Truthy 한 값과 Falsy 한 값은 if 문에서
const value = { a: 1 };
if (value) {
console.log('value 가 Truthy 하네요.');
}
value 가 Truthy 한 값이기 때문에, 콘솔에 메시지가 출력 될 것
반면, value 가 null, undefined, 0, '', NaN 중 하나라면, 나타나지 않을 것
*
단축 평가 (short-circuit evaluation) 논리 계산법
true && true // true
true && false // false
true || false // true
false || true // true
논리 연산자를 사용 할 때 무조건 우리가 true 혹은 false 값을 사용해야 되는 것은 아님
문자열이나 숫자, 객체를 사용 할 수도 있고, Truthy 하냐 Falsy 하냐에 따라 결과가 달라짐
(예)
const dog = {
name: '멍멍이'
};
function getName(animal) {
return animal.name;
}
const name = getName(dog);
console.log(name); // 멍멍이
*
만약 함수에서 animal 값이 제대로 주어졌을 때만 name 을 조회하고,
그렇지 않을때는 그냥 undefined 를 반환하게 하고 싶으면
const dog = {
name: '멍멍이'
};
function getName(animal) {
if (animal) {
return animal.name;
}
return undefined;
}
const name = getName();
console.log(name);
*
&& 연산자로 코드 단축시키기
(예)
const dog = {
name: '멍멍이'
};
function getName(animal) {
return animal && animal.name;
}
const name = getName(dog);
console.log(name); // 멍멍이
*
A && B 연산자를 사용하게 될 때에는
A 가 Truthy 한 값이라면, B 가 결과값이 됩니다.
반면, A 가 Falsy 한 값이라면 결과는 A 가 됩니다.
다음 예시를 한번 살펴보세요.
console.log(true && 'hello'); // hello
console.log(false && 'hello'); // false
console.log('hello' && 'bye'); // bye
console.log(null && 'hello'); // null
console.log(undefined && 'hello'); // undefined
console.log('' && 'hello'); // ''
console.log(0 && 'hello'); // 0
console.log(1 && 'hello'); // hello
console.log(1 && 1); // 1
특정 값이 유효할때에만 어떤 값을 조회하는 작업을 해야 할 때 매우 유용
(예)
const namelessDog = {
name: ''
};
function getName(animal) {
const name = animal && animal.name;
if (!name) {
return '이름이 없는 동물입니다';
}
return name;
}
const name = getName(namelessDog);
console.log(name); // 이름이 없는 동물입니다.
위 코드는 || 연산자를 사용하면 다음과 같이 단축시킬 수 있음
const namelessDog = {
name: ''
};
function getName(animal) {
const name = animal && animal.name;
return name || '이름이 없는 동물입니다.';
}
const name = getName(namelessDog);
console.log(name); // 이름이 없는 동물입니다.
*
A || B 는 만약 A 가 Truthy 할 경우 결과는 A
반면, A 가 Falsy 하다면 결과는 B
*
함수의 기본 파라미터를 설정하는 방법
(예)
원의 넓이를 구하는 함수
function calculateCircleArea(r) {
return Math.PI * r * r;
}
const area = calculateCircleArea(4);
console.log(area); // 50.26548245743669
((Math.PI 는 원주율 파이(π)))
*
이 함수에 r 값을 넣어주지 않으면 NaN
function calculateCircleArea(r) {
return Math.PI * r * r;
}
const area = calculateCircleArea();
console.log(area); // NaN
*
만약에 r 값이 주어지지 않았다면 기본 값을 1을 사용하도록 설정
우리가 지금까지 배운 것들을 활용하면 이렇게 작성 할 수 있습니다.
function calculateCircleArea(r) {
const radius = r || 1;
return Math.PI * radius * radius;
}
const area = calculateCircleArea();
console.log(area); // 3.141592653589793
*
ES5 시절엔 위와 같이 하는게 최선이였는데, ES6 에선 다음과 같이
function calculateCircleArea(r = 1) {
return Math.PI * r * r;
}
const area = calculateCircleArea();
console.log(area); // 3.141592653589793
*
화살표 함수에서도 사용
const calculateCircleArea = (r = 1) => Math.PI * r * r;
const area = calculateCircleArea();
console.log(area); // 3.141592653589793
*
조건문을 조금 더 스마트하게 쓰는 방법
특정 값이 여러 값중 하나인지 확인해야 할 때
function isAnimal(text) {
return (
text === '고양이' || text === '개' || text === '거북이' || text === '너구리'
);
}
console.log(isAnimal('개')); // true
console.log(isAnimal('노트북')); // false
*
비교해야 할 값이 많아질 수록 코드는 길어지는데
이러한 코드를 간단하게 해결 할 수 있는방법은,
배열을 만들고 배열의 includes 함수를 사용하는 것
function isAnimal(name) {
const animals = ['고양이', '개', '거북이', '너구리'];
return animals.includes(name);
}
console.log(isAnimal('개')); // true
console.log(isAnimal('노트북')); // false
*
화살표 함수로 작성할 수도 있습니다.
const isAnimal = name => ['고양이', '개', '거북이', '너구리'].includes(name);
console.log(isAnimal('개')); // true
console.log(isAnimal('노트북')); // false
*
값에 따라 다른 결과물을 반환 해야 할 때
function getSound(animal) {
if (animal === '개') return '멍멍!';
if (animal === '고양이') return '야옹~';
if (animal === '참새') return '짹짹';
if (animal === '비둘기') return '구구 구 구';
return '...?';
}
console.log(getSound('개')); // 멍멍!
console.log(getSound('비둘기')); // 구구 구 구
console.log(getSound('인간')); // ...?
*
if 문의 코드 블록이 한줄짜리라면 { } 를 생략 할 수도 있음
switch case 문을 사용하여 다음과 같이 구현
function getSound(animal) {
switch (animal) {
case '개':
return '멍멍!';
case '고양이':
return '야옹~';
case '참새':
return '짹짹';
case '비둘기':
return '구구 구 구';
default:
return '...?';
}
}
console.log(getSound('개')); // 멍멍!
console.log(getSound('비둘기')); // 구구 구 구
*
이 코드를 더욱 깔끔하게 작성하는 방법
function getSound(animal) {
const sounds = {
개: '멍멍!',
고양이: '야옹~',
참새: '짹짹',
비둘기: '구구 구 구'
};
return sounds[animal] || '...?';
}
console.log(getSound('개')); // 멍멍!
console.log(getSound('비둘기')); // 구구 구 구
*
반면, 값에 따라 실행해야 하는 코드 구문이 다를 때는 객체에 함수를 넣으면 됨
function makeSound(animal) {
const tasks = {
개() {
console.log('멍멍');
},
고양이() {
console.log('고양이');
},
비둘기() {
console.log('구구 구 구');
}
};
if (!tasks[animal]) {
console.log('...?');
return;
}
tasks[animal]();
}
getSound('개'); //멍멍
getSound('비둘기'); //구구 구 구
*
비구조화 할당 문법을 사용 : 객체 안에 있는 값을 추출해서 변수 혹은 상수로 바로 선언
const object = { a: 1, b: 2 };
const { a, b } = object;
console.log(a); // 1
console.log(b); // 2
*
함수의 파라미터에서도 비구조화 할당
const object = { a: 1, b: 2 };
function print({ a, b }) {
console.log(a);
console.log(b);
}
print(object); // 1 2
*
여기서 만약 b 값이 주어지지 않았다고 가정해봅시다.
두번째 출력에서 undefined
*
비구조화 할당시 기본값 설정
이러한 상황에 b 값에 기본 값을 주고 싶다면 이렇게 해줄 수 있습니다.
const object = { a: 1 };
function print({ a, b = 2 }) {
console.log(a);
console.log(b);
}
print(object);
// 1
// 2
*
비구조화 할당시 이름 바꾸기
(예)
const animal = {
name: '멍멍이',
type: '개'
};
const nickname = animal.name;
console.log(nickname); // 멍멍이
*
이름이 서로 다를 때는 문자를 사용해서 이름을 바꿔줄 수 있습니다.
const animal = {
name: '멍멍이',
type: '개'
};
const { name: nickname } = animal
console.log(nickname);
'animal 객체 안에 있는 name 을 nickname 이라고 선언하겠다.' 라는 의미
*
배열 비구조화 할당
(예)
const array = [1, 2];
const [one, two] = array;
console.log(one); //1
console.log(two); //2
*
객체 비구조화 할당과 마찬가지로, 기본값 지정이 가능
const array = [1];
const [one, two = 2] = array;
console.log(one); //1
console.log(two); //2
*
깊은 값 비구조화 할당
(예)
const deepObject = {
state: {
information: {
name: 'velopert',
languages: ['korean', 'english', 'chinese']
}
},
value: 5
};
여기서, name, languages, value 값들을 밖으로 꺼내주고 싶다면
첫번째는 비구조화 할당 문법을 두번 사용
const deepObject = {
state: {
information: {
name: 'velopert',
languages: ['korean', 'english', 'chinese']
}
},
value: 5
};
const { name, languages } = deepObject.state.information;
const { value } = deepObject;
const extracted = {
name,
languages,
value
};
console.log(extracted); // {name: "velopert", languages: Array[3], value: 5}
*
const extracted = {
name,
languages,
value
}
이 코드는 다음과 같음
const extracted = {
name: name,
languages: languages,
value: value
}
두번째 방법, 한번에 모두 추출하는 방법
const deepObject = {
state: {
information: {
name: 'velopert',
languages: ['korean', 'english', 'chinese']
}
},
value: 5
};
const {
state: {
information: { name, languages }
},
value
} = deepObject;
const extracted = {
name,
languages,
value
};
console.log(extracted);
*
spread : 객체 혹은 배열을 펼칠 수 있음.
(예)
const slime = {
name: '슬라임'
};
const cuteSlime = {
name: '슬라임',
attribute: 'cute'
};
const purpleCuteSlime = {
name: '슬라임',
attribute: 'cute',
color: 'purple'
};
console.log(slime);
console.log(cuteSlime);
console.log(purpleCuteSlime);
*
아까 코드는 spread … 문법을 사용하면
const slime = {
name: '슬라임'
};
const cuteSlime = {
...slime,
attribute: 'cute'
};
const purpleCuteSlime = {
...cuteSlime,
color: 'purple'
};
console.log(slime);
console.log(cuteSlime);
console.log(purpleCuteSlime);
*
spread 연산자는 배열에서도 사용 할 수 있습니다.
const animals = ['개', '고양이', '참새'];
const anotherAnimals = [...animals, '비둘기'];
console.log(animals);
console.log(anotherAnimals);
기존의 animals 는 건드리지 않으면서, 새로운 anotherAnimals 배열에
animals 가 가지고 있는 내용을 모두 집어넣고, '비둘기' 라는 항목을 추가적으로
*
배열에서 spread 연산자를 여러번 사용 할 수도 있음
const numbers = [1, 2, 3, 4, 5];
const spreadNumbers = [...numbers, 1000, ...numbers];
console.log(spreadNumbers); // [1, 2, 3, 4, 5, 1000, 1, 2, 3, 4, 5]
*
Rest : 객체, 배열, 그리고 함수의 파라미터에서 사용이 가능
(예)객체에서의 rest
const purpleCuteSlime = {
name: '슬라임',
attribute: 'cute',
color: 'purple'
};
const { color, ...rest } = purpleCuteSlime;
console.log(color);
console.log(rest);
rest 안에 name 값을 제외한 값이 들어있음
rest 는 객체와 배열에서 사용 할 때는 이렇게 비구조화 할당 문법과 함께 사용
주로 위와 같이 rest 라는 키워드를 사용하게 되는데,
추출한 값의 이름이 꼭 rest 일 필요는 없음
const purpleCuteSlime = {
name: '슬라임',
attribute: 'cute',
color: 'purple'
};
const { color, ...cuteSlime } = purpleCuteSlime;
console.log(color);
console.log(cuteSlime);
* attribute 까지 없앤 새로운 객체를 만들고 싶다면
const purpleCuteSlime = {
name: '슬라임',
attribute: 'cute',
color: 'purple'
};
const { color, ...cuteSlime } = purpleCuteSlime;
console.log(color);
console.log(cuteSlime);
const { attribute, ...slime } = cuteSlime;
console.log(attribute);
console.log(slime);
*
배열에서의 rest
const numbers = [0, 1, 2, 3, 4, 5, 6];
const [one, ...rest] = numbers;
console.log(one);
console.log(rest);
배열 비구조화 할당을 통하여 원하는 값을 밖으로 꺼내고, 나머지 값을 rest 안에.
;
*
함수 파라미터에서의 rest
function sum(a, b, c, d, e, f, g) {
let sum = 0;
if (a) sum += a;
if (b) sum += b;
if (c) sum += c;
if (d) sum += d;
if (e) sum += e;
if (f) sum += f;
if (g) sum += g;
return sum;
}
const result = sum(1, 2, 3, 4, 5, 6);
console.log(result);//28
위에서의 sum 함수는 7개의 파라미터를 받아오는데,
아래에서 사용 할때에는 6개만
그러면, g 값이 undefined 가 되기 때문에 sum 에 더하는 과정에서
+= undefined 를 하게 되면 결과는 NaN
*
함수의 파라미터가 몇개가 될 지 모르는 상황에서 rest 사용
function sum(...rest) { à 모두 받아옴
return rest;
}
const result = sum(1, 2, 3, 4, 5, 6);
console.log(result);
*
result 가 가르키고 있는 것은 함수에서 받아온 파라미터들로 이루어진 배열
function sum(...rest) {
return rest.reduce((acc, current) => acc + current, 0);
}
const result = sum(1, 2, 3, 4, 5, 6);
console.log(result); // 21
*
함수 인자와 spread
const myFunction(a) { // 여기서 a 는 파라미터
console.log(a); // 여기서 a 는 인자
}
myFunction('hello world'); // 여기서 'hello world' 는 인자
함수에서 값을 읽을때, 그 값들은 파라미터
함수에서 값을 넣어줄 때, 그 값들은 인자
*
sum함수를 사용 할 때 인자 부분에서 spread 를 사용하면
function sum(...rest) {
return rest.reduce((acc, current) => acc + current, 0);
}
const numbers = [1, 2, 3, 4, 5, 6];
const result = sum(...numbers);
console.log(result);
*
Scope 변수 혹은 함수를 선언하게 될 때 해당 변수 또는 함수가 유효한 범위
(1) Global (전역) Scope: 코드의 모든 범위에서
(2) Function (함수) Scope: 함수 안에서
(3) Block (블록) Scope: if, for, switch 등 특정 블록 내부에서
*
(예시)
const value = 'hello!';
function myFunction() {
console.log('myFunction: ');
console.log(value);
}
function otherFunction() {
console.log('otherFunction: ');
const value = 'bye!';
console.log(value);
}
myFunction();
otherFunction();
console.log('global scope: ');
console.log(value);
(결과)
myfunction!
hello!
otherfunction!
bye!
global scope:
hello!
맨 윗줄에서 선언된 value 값은 Global Scope 로 선언된 값
myFunction 에서 바로 사용을
otherFunction 에서는 함수 내부에서 value 값을 'bye!' 로 새로 선언
value 라는 값은 Function Scope 로 지정, 해당 값은 otherFunction 내부에서만 유효
*
(예시)
const value = 'hello!';
function myFunction() {
const value = 'bye!';
const anotherValue = 'world';
function functionInside() {
console.log('functionInside: ');
console.log(value);
console.log(anotherValue);
}
functionInside();
}
myFunction()
console.log('global scope: ');
console.log(value);
console.log(anotherValue);
(결과)
functionInside:
bye!
world
global scope:
hello!
error in sandbox :
myFunction 내부에 anotherValue 라는 새로운 값을 선언 functionInside 라는 함수도 선언
functionInside 함수에서는 myFunction 에서 선언한 value 값과 anotherValue 값을 조회 할 수 있음
반면, myFunction 밖에서는 anotherValue 를 조회 할 수 없음
*
(예시)
const value = 'hello!';
function myFunction() {
const value = 'bye!';
if (true) {
const value = 'world';
console.log('block scope: ');
console.log(value);
}
console.log('function scope: ');
console.log(value);
}
myFunction();
console.log('global scope: ');
console.log(value);
(결과)
block scope:
world
function scope:
bye!
global scope:
hello!
const 로 선언한 값은 Block Scope 로 선언
if 문 같은 블록 내에서 새로운 변수/상수를 선언하게 된다면,
해당 블록 내부에서만 사용이 가능, 블록 밖의 범위에서 똑같은 이름을 가진 값이 있다고 해도 영향없음
let 또한 마찬가지
*
(예시) var
var value = 'hello!';
function myFunction() {
var value = 'bye!';
if (true) {
var value = 'world';
console.log('block scope: ');
console.log(value);
}
console.log('function scope: ');
console.log(value);
}
myFunction();
console.log('global scope: ');
console.log(value);
(결과)
block scope:
world
function scope:
world
global scope:
hello!
var 는 Function Scope 로 선언
if 문 블록 내부에서 선언한 value 값이 블록 밖의 value 에도 영향을 미침
*
Hoisting : 자바스크립트에서 아직 선언되지 않은 함수/변수를 "끌어올려서" 사용
(예시)
myFunction();
function myFunction() {
console.log('hello world!');
}
myFunction 함수를 선언하기 전에, myFunction() 을 호출
함수가 아직 선언되지 않았음에도 불구하고 코드는 정상적으로 작동
이게 잘 작동하는 이유는, 자바스크립트 엔진이 위 코드를 해석하는 과정에서
function myFunction() {
console.log('hello world!');
}
myFunction();
이러한 현상을, Hoisting
*
변수도 호이스팅이 됨
(예시)
console.log(number);
var number = 2;
(결과)
undefined 가 출력
자바스크립트 해석
var number;
console.log(number);
number = 2;
*
반면, const 와 let 은 hoisting 이 발생하지 않고, 에러가 발생
Codesandbox 에서는 자바스크립트가 Babel 이라는 도구에 의하여
const 와 let 이 var 로 변환되기 때문에 오류가 발생하지 않음
Hoisting 이 발생하는 코드는 이해하기 어렵기 때문에 비추
var 대신 const, let 을 위주로 사용 추천
프론트엔드 개발 올인원 패키지 with React Online. | 패스트캠퍼스
프론트엔드 개발 러닝패스, 이 강의 패키지 하나로 끝낸다!
www.fastcampus.co.kr
'패스트캠퍼스 챌린지 < 프론트엔드-react>' 카테고리의 다른 글
[패스트캠퍼스 수강후기] 프론트엔드 강의 32회차 미션 (0) | 2020.07.30 |
---|---|
[패스트캠퍼스 수강후기] 프론트엔드 강의 31회차 미션 (0) | 2020.07.29 |
[패스트캠퍼스 수강후기] 프론트엔드 강의 29회차 미션 (0) | 2020.07.27 |
[패스트캠퍼스 수강후기] 프론트엔드 강의 28회차 미션 (0) | 2020.07.26 |
[패스트캠퍼스 수강후기] 프론트엔드 강의 27회차 미션 (0) | 2020.07.25 |