[자료구조와 자료형]
- iterable 객체 모든 객체에 for of 반복문을 적용할 수 있음.
예시 코드)
let range = {
from: 1,
to: 5
};
// 1. for..of 최초 호출 시, Symbol.iterator가 호출됩니다.
range[Symbol.iterator] = function() {
// Symbol.iterator는 이터레이터 객체를 반환합니다.
// 2. 이후 for..of는 반환된 이터레이터 객체만을 대상으로 동작하는데, 이때 다음 값도 정해집니다.
return {
current: this.from,
last: this.to,
// 3. for..of 반복문에 의해 반복마다 next()가 호출됩니다.
next() {
// 4. next()는 값을 객체 {done:.., value :...}형태로 반환해야 합니다.
- 왜 굳이 객체 형태로 반환하는 것인가..
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}
반복문으로 객체를 불러들일 때마다 객체 안의 메서드도 반복적으로 불려져서 메서드 반복 실행이 가능해진다. range[Symbol.iterator] 를 객체형으로 let range{ } 필드안에 넣어줘도 된다.
대표적으로 배열, 문자열은 내장 이터러블이다. for of 를 사용하여 문자열을 입력하면 문자열이 차례로 호출된다.
- iterable 객체가 아닌 경우 (유사 배열)
let arrayLike = { // 인덱스와 length프로퍼티가 있음 => 유사 배열 0: "Hello", 1: "World", length: 2 };위의 경우는 유사배열이지만 이터러블은 아니다.
let arr = Array.from(arrayLike); // (*)유사배열의 경우 Array.from()을 사용하여 배열로 만들어줄 수 있다.
- 정리 객체 – 키가 있는 컬렉션을 저장함 배열 – 순서가 있는 컬렉션을 저장함
- 맵 : 매핑시켜주는 자료구조 기본 프로퍼티 & 메서드 new Map() – 맵을 만듭니다. map.set(key, value) – key를 이용해 value를 저장합니다. map.get(key) – key에 해당하는 값을 반환합니다. key가 존재하지 않으면 undefined를 반환합니다. map.has(key) – key가 존재하면 true, 존재하지 않으면 false를 반환합니다. map.delete(key) – key에 해당하는 값을 삭제합니다. map.clear() – 맵 안의 모든 요소를 제거합니다. map.size – 요소의 개수를 반환합니다.
키를 set 하고 get으로 불러오는 방식임. 객체를 key로 사용할 수 있다.
맵의 체이닝 가능
map.set('1', 'str1')
.set(1, 'num1')
.set(true, 'bool1');
맵의 key, value모두 이터러블이기 때문에 for of 구문으로 반복적으로 순회할 수 있다. (key, value) 쌍으로 저장 된 것을 entries 라고 한다. for(let entry of recipeMap) 이런 식으로 사용하면 key, value 쌍으로 출력해준다.
Object.entries(obj) 이런식으로 사용하여 객체->맵으로 변경 가능하다. Object.fromEntries 맵-> 객체 변경가능
-
- 셋
- 중복을 허용하지 않는 값을 모아놓은 특별한 컬렉션 new Set(iterable) – 셋을 만듭니다. 이터러블 객체를 전달받으면(대개 배열을 전달받음) 그 안의 값을 복사해 셋에 넣어줍니다. set.add(value) – 값을 추가하고 셋 자신을 반환합니다. set.delete(value) – 값을 제거합니다. 호출 시점에 셋 내에 값이 있어서 제거에 성공하면 true, 아니면 false를 반환합니다. set.has(value) – 셋 내에 값이 존재하면 true, 아니면 false를 반환합니다. set.clear() – 셋을 비웁니다. set.size – 셋에 몇 개의 값이 있는지 세줍니다.
이터러블이기 때문에 for of 가능 forEach 사용도 가능한데 다음과 같다.
forEach((vlaue, valueAgain, set) => {
alert(value);
})
valueAgain 을 써줘야하는 이는 맵과의 호환성을 좋게하기 위해서이다.
- 맵과 셋은 가비지 컬렉터의 대상이 되지 않는다. 참조가 아닌 메모리에 곧장 저장인듯하다.
예를들어..
let john = { name: "John" }; let array = [ john ]; john = null; // 참조를 null로 덮어씀 // john을 나타내는 객체는 배열의 요소이기 때문에 가비지 컬렉터의 대상이 되지 않습니다. // array[0]을 이용하면 해당 객체를 얻는 것도 가능합니다. alert(JSON.stringify(array[0]));john이라는 객체는 참조로 불러올텐데 이를 array에 저장하게 되면 그 안에 있는 요소들을 메모리에 저장하는 듯하다. 그러면 john의 참조를 끊어버려도 배열의 값은 남아있는 것이다.
-
위크맵, 위크셋 맵과 셋은 가비지 컬렉터의 대상이 아니였으나.. 위크맵과 위크셋은 가비지 컬렉터의 대상이 된다. 그러나 위크맵과 위크셋은 키를 객체로만 받을 수 있다. ※ 위크맵, 위크셋은 반복이 불가능하다.
-
맵과 캐싱 캐시를 사용할 때, 위크맵으로 구성하면 된다. 캐시의 청소를 위해 위크맵으로 사용한다.
-
일반 객체에서 key, value, entry를 사용하고 싶을 때 Object.keys(obj) – 객체의 키만 담은 배열을 반환합니다. Object.values(obj) – 객체의 값만 담은 배열을 반환합니다. Object.entries(obj) – [키, 값] 쌍을 담은 배열을 반환합니다.
let user = { name: "John", age: 30 }; Object.keys(user) = ["name", "age"] Object.values(user) = ["John", 30] Object.entries(user) = [ ["name","John"], ["age",30] ]아래와 같이 객체를 배열로 바꾼 후 다시 배열을 객체로 바꿔서 사용할 수 있다. ```javascript let prices = { banana: 1, orange: 2, meat: 4, };
let doublePrices = Object.fromEntries( // 객체를 배열로 변환해서 배열 전용 메서드인 map을 적용하고 fromEntries를 사용해 배열을 다시 객체로 되돌립니다. Object.entries(prices).map(([key, value]) => [key, value * 2]) );
alert(doublePrices.meat); // 8
-----------------------------------------------------------------------------------------------------------------------
8. 구조분해 할당
객체나 배열을 변수로 '분해’할 수 있게 해주는 특별한 문법
```javascript
let [firstName, surname] = "Bora Lee".split(' ');
firstName 에 Bora surname 에 Lee 할당
- 변수 교환 트릭이 가능하다. let guest = “Jane”; let admin = “Pete”;
// 변수 guest엔 Pete, 변수 admin엔 Jane이 저장되도록 값을 교환함 [guest, admin] = [admin, guest];
-
나머지 값 가져오기 let [name1, name2, …rest] = [“Julius”, “Caesar”, “Consul”, “of the Roman Republic”]; rest[0],[1].. 순으로 뒤에 Consul 부터 들어간다.
-
객체 분해 ```javascript let options = { title: “Menu”, width: 100, height: 200 };
let {title, width, height} = options;
alert(title); // Menu alert(width); // 100 alert(height); // 200
프로퍼티 하나씩 이름을 찾아서 들어간다.
순서가 바뀌어도 된다.
그러므로 원하는 정보만 빼오는 것도 가능하다.
- 객체에서 rest 사용할 때
```javascript
let options = {
title: "Menu",
height: 200,
width: 100
};
// title = 이름이 title인 프로퍼티
// rest = 나머지 프로퍼티들
let {title, ...rest} = options;
// title엔 "Menu", rest엔 {height: 200, width: 100}이 할당됩니다.
alert(rest.height); // 200
alert(rest.width); // 100
- 함수 매개변수에 객체 분할 적용하면 아래와 같다. ```javascript let options = { title: “My menu”, items: [“Item1”, “Item2”] };
function showMenu({
title = “Untitled”,
width: w = 100, // width는 w에,
height: h = 200, // height는 h에,
items: [item1, item2] // items의 첫 번째 요소는 item1에, 두 번째 요소는 item2에 할당함
}) {
alert( ${title} ${w} ${h} ); // My Menu 100 200
alert( item1 ); // Item1
alert( item2 ); // Item2
}
showMenu(options);
이와 같이 매개변수에 객체를 입력하게 하여 출력해줄 수 있다.
9. Date
- new Date() 로 사용, 기본적으로 현재 시간을 가져옴
- Date(0)을 사용하면 1970년 1월 1일 을 가져옴.
- let date = new Date("2017-01-26"); 구문분석해서 파악함
- new Date(year, month, date, hours, minutes, seconds, ms)
* year는 반드시 네 자리 숫자여야 합니다. 2013은 괜찮고 98은 괜찮지 않습니다.
* month는 0(1월)부터 11(12월) 사이의 숫자여야 합니다.
* date는 일을 나타내는데, 값이 없는 경우엔 1일로 처리됩니다.
* hours/minutes/seconds/ms에 값이 없는 경우엔 0으로 처리됩니다.
- getFullYear()
연도(네 자릿수)를 반환합니다.
- getMonth()
월을 반환합니다(0 이상 11 이하).
- getDate()
일을 반환합니다(1 이상 31 이하). 어! 그런데 메서드 이름이 뭔가 이상하네요.
- getHours(), getMinutes(), getSeconds(), getMilliseconds()
시, 분, 초, 밀리초를 반환합니다.
- getDay()
일요일을 나타내는 0부터 토요일을 나타내는 6까지의 숫자 중 하나를 반환합니다
- 자동 고침
해당 달의 일수가 넘어가거나 2월의 윤년등을 계산 알아서 해준다.
일수를 0으로 입력한 경우 전날로 설정된다.
시간의 차이를 구해서 성능을 분석해야하는 (벤치마크 테스트) 경우 그냥 무작정 - 연산보다, getTime()을 사용하여 -연산 사용하는게 더 낫다.
형변환 할 필요가 없기 때문...
10. JSON 과 메서드
JSON.stringify – 객체를 JSON으로 바꿔줌.
JSON.parse – JSON을 객체로 바꿔줌.
* JSON 의 특징
- 문자열은 큰 따옴표로 감쌀 것.
- 객체 프로퍼티 이름은 큰 따옴표로 감쌀 것.
* JSON.stringify 를 무시하는 경우
- 함수 프로퍼티 (메서드)
- 심볼형 프로퍼티 (키가 심볼인 프로퍼티)
- 값이 undefined인 프로퍼티
* 두개의 객체가 서로를 참조하여 순환 구조인 경우는 stringify가 안먹음
* replacer로 대체 하면 순환중에도 가능하다.
```javascript
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup references room
};
room.occupiedBy = meetup; // room references meetup
alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
/*
{
"title":"Conference",
"participants":[{"name":"John"},{"name":"Alice"}],
"place":{"number":23}
}
*/
room -> number : 23 -> occupiedBy => meetup => title : Conference participants : .. place : room => number… occupiedBy 에서 순환이 시작되므로 해당 값은 안쓰고 값들로 받으면 됨.
stringify 에서 space 는 json을 만들어줄 때 사용하는 들여쓰기 공백 2~4로 설정하면 됨
-
toJSON 을 사용하면 객체를 json으로 바꿔줄 때 값으로 바꿔주게 할 수 있다.\
-
JSON.parse(str, [reviver]); JSON -> 객체로 디코딩 str : 객체에서 JSON 형식의 문자열 reviver : Date 같은 경우 예외 처리를 해줄 때 사용
[함수 심화학습]
- 재귀
두 가지 사고방법
- x를 n제곱 해주는 함수를 만든다 했을 때.
1) for루프를 통해서 작업 : 일반적임
2) 재귀적인 사고. 작업 단순화 됨
function pow(x, n) { if (n == 1) return x; else return x * pow(x, n - 1); } alert( pow(2, 3) ); // 8 - 재귀적 순회 맨 하위 구조까지 내려가서 탐색해야 한다면 재귀적 순회가 적절하다.
- 말단이면 값을 출력
- 말단이 아니면 그 하위 객체를 함수 재귀 위와 같이 구현하면 됨. 링크드 리스트도 구현이 가능하다.
- x를 n제곱 해주는 함수를 만든다 했을 때.
1) for루프를 통해서 작업 : 일반적임
2) 재귀적인 사고. 작업 단순화 됨
-
나머지 매개변수 함수 정의에 상관 없이 함수 인수에는 제약이 없다. … 의 의미 : 나머지 매개변수들을 한데 모아 배열에 집어넣어라. …rest는 항상 마지막에 있어야 함.
-
arguments 변수 ```javascript function showName() { alert( arguments.length ); alert( arguments[0] ); alert( arguments[1] );
// arguments는 이터러블 객체이기 때문에 // for(let arg of arguments) alert(arg); 를 사용해 인수를 나열할 수 있습니다. }
// 2, Julius, Caesar가 출력됨 showName(“Julius”, “Caesar”);
// 1, Bora, undefined가 출력됨(두 번째 인수는 없음) showName(“Bora”);
매개 변수가 없어도 인수를 차례로 받게 해줌
- 전개 문법 let arr = [3, 5, 1]; alert( Math.max(…arr) ); // 5 (전개 문법이 배열을 인수 목록으로 바꿔주었습니다.) ``` 배열같은 경우는 max 가 안먹기 때문에 배열의 각 값을 받아오는 …arr 를 사용해주면 됨.
-
-
변수의 유효범위, 클로저 자바스크립트는 함수 지향 언어, 함수란 어디서든 불러올 수 있기 때문에 다양한 기능의 코딩이 가능 함수 내부에선 함수 외부의 변수에 접근이 가능하다. 그런데 외부 변수의 변경 시점과 함수 호출 시점에 따른 시나리오별로 살펴봐야한다.
-
코드 블록 {……} 중괄호로 묶인 애를 코드블록이라고 한다. 블록안이 기본임, 블록에서 나가면 변수는 사라짐 또한 함수 안에 함수 만드는 것도 가능함. (중첩 함수)
중첩함수는 단독으로 반환 가능하다. -
렉시컬 환경
step1) 변수
렉시컬 환경(Lexical Environment) : 내부 숨김 연관 객체(internal hidden associated object) 안보이지만 렉시컬 환경이라는 것도 결국 객체처럼 동작한다는 것이 핵심!
-
렉시컬 환경은 두부분으로 구성됨.
- 환경 레코드(Environment Record) – 모든 지역 변수를 프로퍼티로 저장하고 있는 객체, this 값과 같은 기타 정보도 여기에 저장
-
외부 렉시컬 환경(Outer Lexical Environment) 에 대한 참조 – 외부 코드와 연관됨
-
전역 렉시컬 환경 스크립트 전체와 관련된 렉시컬 환경. //환경레코드 : 변수 저장 (global var) //외부참조 : 전역 환경에는 외부참조 없음
- 변수 선언과 할당에 따른 렉시컬 환경의 변화 1) 스크립트 시작, 선언한 변수 전체가 렉시컬 환경에 올라감. 변수 선언이 없으면 uninitialized 상태 1) 변수가 let과 함께 선언되면 렉시컬 환경에서 인식해서 undefined값으로 바꿈 2) 값이 할당 되면 해당 값으로 바꿔줌 3) 값이 수정되면 해당 값으로 바꿈.
step2) 함수 선언문
- 함수도 값임!
- “다만 함수 선언문(function declaration)으로 선언한 함수는 일반 변수와는 달리 바로 초기화된다는 점에서 차이가 있음”
- 위 말은 변수는 uninitialized 상태 -> 선언된 후 undefined 상태로 변경되는데
- 함수는 렉시컬 환경이 “만들어짐과 동시에” 함수를 다 찾아서 function 으로 프로퍼티 정보를 입력함.
- ※ 그런데 이는 함수 선언문으로 정의한 함수에만 적용됨.
- let say = function(name) 같이 함수를 변수에 할당한 함수 표현식은 해당x
step3) 내부와 외부 렉시컬 환경
함수 호출 시 전체 렉시컬 환경 말고 함수 렉시컬 환경이 또 만들어짐 이 렉시컬 환경에는 //넘겨받는_매개변수 //지역변수 가 저장됨
//예시
let phrase = "Hello";
/////////내부_렉시컬////////////
function say(name){
alert(`${phrase}, ${name}`);
}
/////////내부_렉시컬////////////
say("John");
위와 같은 상황에서
//내부_렉시컬
name:"John"
//외부로부터_받은_변수
phrase:"Hello"
변수 접근시 검색범위 : 먼저 내부 렉시컬 환경, (내부 렉시컬 환경에서 원하는 변수를 찾지 못하면) => 내부 환경이 참조하는 외부 렉시컬 환경으로 확장 (전역 렉시컬 환경으로 확장될 때까지 반복)
step4) 반환 함수
**모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억한다. 아까의 예시
function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
makeCounter()를 호출 시 새로운 렉시컬 환경 객체 만들어지고 필요한 변수들이 저장됨.
위의 경우 //makeCounter_내부렉시컬 count : 0 //전역_렉시컬 makeCounter : function counter : undefined
추가로 중첩함수 return function()의 중첩함수는 자신의 렉시컬 환경을 기억한다. function()에서 [[Environment]]라는 애가 생성되는데 얘가 makeCounter 내부 렉시컬환경을 기억하는 것이다.
중첩함수 안으로 들어오면 count++; 라는 애를 만나는데 count는 먼저 내부렉시컬에서 찾는데. //1.중첩함수_내부렉시컬 //2.makeCounter_내부렉시컬 //3.전역_렉시컬 위 순으로 검색 ‘1’에는 없음. ‘2’에서 발견 “변숫값 갱신은 변수가 저장된 렉시컬 환경에서 이뤄짐.” count++ 의 실행 결과는 //2.makeCounter_내부렉시컬 에 저장된다. 그러므로 counter()를 여러번 실행하면 //2.makeCounter_내부렉시컬 에서의 count 값을 불러오므로 count 값이 초기화 되지 않고 그대로 유지되는 것이다. (javascript의 특징)
/////////////////클로저란?(필수용어)/////////////////////////////////////////
클로저는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수
자바스크립트에선 모든 함수가 자연스럽게 클로저가 됨. (예외 : new Function 문법)
함수는 [[Environment]]를 이용해 자신이 어디서 만들어졌는지를 기억
함수 내부의 코드는 [[Environment]]를 사용해 외부 변수에 접근함.
그러니 모든 함수가 클로저가 될 수 있는 것임!
/////////////////////////////////////////////////////////////////////////////
- 가비지 컬렉션
렉시컬 환경의 메모리 제거는 어떻게 되는가.
함수 호출이 끝나면 제거됨. 그래서 함수호출이 끝나면 함수 안의 변수들도 삭제되는 것임
* 'var'는 블록 스코프가 없음!
코드 블록이 함수 안에 있으면 var는 함수 안에서만 동작
var는 중첩으로 선언해도 마지막으로 선언한 대로 저장되고 에러 안뜸
또한 사용하고 선언해도 된다.
* 함수 (function{...}) 으로 사용하면 함수 선언과 동시에 실행된다.
괄호가 없으면 실행 안된다. 그냥 함수 선언문으로 인식하기 때문이다.
// IIFE를 만드는 방법
(function() {
alert("함수를 괄호로 둘러싸기");
})();
(function() {
alert("전체를 괄호로 둘러싸기");
}());
!function() {
alert("표현식 앞에 비트 NOT 연산자 붙이기");
}();
+function() {
alert("표현식 앞에 단항 덧셈 연산자 붙이기");
}();
-
전역 객체 window. var는 접근 가능한데 let은 접근 불가능하다 Promise를 통해 구식 브라우저인지 최근것인지 확인 가능
-
- 객체로서의 함수, 기명함수 표현식
- 자바스크립트에서 함수는 결국 값임.
- 함수의 자료형? 객체
- “함수는 호출이 가능한 ‘행동객체’이다.”
-
- 함수는 호출이 가능하다.
-
객체처럼 프로퍼티를 추가,제거 참조를 통해 전달 할 수 있다.
-
이름을 표현해주는 name sayHi.name : sayHi
-
length는 매개변수의 개수 반환함
-
커스텀 프로퍼티 자체적으로 만든 프로퍼티를 함수에 추가할 수 있다. ※ 프로퍼티는 변수가 아님! ```javascript function makeCounter() {
// let count = 0 대신 아래 메서드(프로퍼티)를 사용함
function counter() { return counter.count++; };
counter.count = 0;
return counter; }
let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 ```
- 함수 만드는 또다른 방법 (거의 사용 안함)
let sum = new Function('a', 'b', 'return a + b'); alert( sum(1, 2) ); // 3 - 호출 스케줄링
1) setTimeout을 이용해 일정 시간이 지난 후에 함수를 실행하는 방법
setTimeout(sayHi, 1000); // 1초후에 실행
setTimeout(sayHi, 1000, “홍길동”, “안녕하세요.”); // 홍길동 님, 안녕하세요.
// sayHi에 인수가 필요한 경우 다음과 같이 넘겨줌
setTimeout(“alert(
안녕하세요.)”, 1000); // 이렇게 문자열로 표현하는 것보단 “ setTimeout(() => alert(‘안녕하세요.’), 1000); // 이렇게 화살표 형으로 쓰는게 더 나음- 함수 끝에 () 붙이지 말 것.
2) setInterval을 이용해 일정 시간 간격을 두고 함수를 실행하는 방법
// 2초 간격으로 메시지를 보여줌
let timerId = setInterval(() => alert('째깍'), 2000);
// 5초 후에 정지
setTimeout(() => { clearInterval(timerId); alert('정지'); }, 5000);
* 위 두 함수는 중첩으로 사용할 수 있다.
- func.call func.apply 사용
func(1, 2, 3);
func.call(obj, 1, 2, 3)
위의 두 함수 호출은 같다.
call 문법의 첫번째 인수는 this와 같다.
함수 안에 this.메서드 를 사용하는 경우 this를 명시해줘야할 때 쓴다.
let result = func.call(this, x); // 이젠 'this'가 제대로 전달됩니다.위와 같이 사용하면 된다.
- 인수가 두개 이상이라면
let result = func.call(this, ...arguments);를 사용한다.
- func.apply 사용
func.call(context, ...args); // 전개 문법을 사용해 인수가 담긴 배열을 전달하는 것과 func.apply(context, args); // call을 사용하는 것은 동일합니다.위의 두 문법은 동일하다.
- 인수가 두개 이상이라면
- 함수 바인딩
- this가 사라지는 issue
```javascript
let user = {
firstName: “John”,
sayHi() {
alert(
Hello, ${this.firstName}!); } };
setTimeout(user.sayHi, 1000); // Hello, undefined!
setTimeout 함수에 user.sayHi가 전달될 때는 ```javascript sayHi() { alert(`Hello, ${this.firstName}!`); }딱 이 부분만 전달 되기 때문이다. 그러니 this를 못읽는 것이다.
- 해결방법
1) 래퍼씌워주기
setTimeout(function() { user.sayHi(); // Hello, John! }, 1000);이런식으로 랩퍼를 씌워주는 것. 이는
setTimeout(() => user.sayHi(), 1000); // Hello, John!와도 같다.
2) bind
func 함수가 있고 user 객체가 있을 때
- this가 사라지는 issue
```javascript
let user = {
firstName: “John”,
sayHi() {
alert(
let funcUser = func.bind(user);
funcUser(); // John
이렇게 사용하면 어디서든 사용할 수 있다.
아래를 보면.. ```javascript
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
let sayHi = user.sayHi.bind(user); // (*)
// 이제 객체 없이도 객체 메서드를 호출할 수 있습니다.
sayHi(); // Hello, John!
setTimeout(sayHi, 1000); // Hello, John!
// 1초 이내에 user 값이 변화해도
// sayHi는 기존 값을 사용합니다.
user = {
sayHi() { alert("또 다른 사용자!"); }
}; ```
내가 봤을 땐
user.sayHi.bind(user)의 의미는
sayHi 안에있는 this에 user를 고정시키겠다는 의미이다.
- bind 의 부분적용
let double = mul.bind(null, 2);
이런식으로 하면 double(3) 이렇게 사용하면 2 * 3 을 출력하는 함수로 만들어지는 것이다.
- 컨텍스트 없이 부분적용
함수를 구현하여 사용하면 되는데 function.call()을 사용하면 어렵지 않게 구현 가능하다.
- 화살표 함수 다시 살펴보기 화살표 함수의 this는 외부에서 가져오기 때문에 외부의 프로퍼티에 접근이 가능하다.