...rest
일반 함수에서 arguments라는 녀석을 본 기억이 있었다
이는 일반 함수에 내장되어 있어서 언제든 자유롭게 사용할 수 있었다
하지만 arrow function은 일반 함수와 다르게 이런 arguments라는 녀석이 존재하지 않는다
그렇다면 arrow function에서 전달받은 인수들을 어떻게 나열할까?
바로 spread syntax를 활용하는 방법이다
const a = (...rest) => {
let total = 0;
rest.forEach((price) => total += price);
}
//reduce
total = rest.reduce((acc, cur) => {
return acc + cur;
},0);
이러면 rest에는 인수로 전달받은 모든 값들이 '배열'로 저장되므로 forEach와 reduce를 사용할 수 있게된다
const a = (a,...rest) => {}
위의 코드는 첫 번째로 받은 인수는 a라는 매개변수에 할당하고 나머지는 rest에 배열로 저장하게 된다
arrow function에서 매개변수와 return
arrow function은 그 동안 사용하는 생김새만 다를 뿐 매개변수와 return을 사용해서 함수를 작성하는 것은 모두가 알고 있는 사실이다
하지만 특수한 상황에서 매개변수를 묶어주는 () 와 return과 {}을 생략해도 된다
특수한 상황이란? 매개변수가 하나일때와 본문이 한 줄일때를 의미한다
물론 각각의 경우는 각각 적용한다
total = rest.reduce((acc,cur) => acc+cur);
---
rest.forEach(price => total += price);
this의 활용
this는 기본적으로 실행 컨텍스트에 따라 달라진다
예를들어 global 즉, 전역에 선언된 함수들의 this는 전역 실행 컨텍스트에서 호출됐으므로 window를 반환한다

이렇게 확인하기 싫다면 다른 방법도 존재한다

앞에서 '호출'이라는 단어와 함께 this의 위치를 예측했었다
그렇다면 this는 어떻게 할당되는가? 를 생각해보면 답은 나왔다
바로 자신을 호출한 객체를 반환하는 것이다
하지만 이는 일반 함수에 대한 얘기다
전에 얘기했듯 arrow function은 this를 내장하고 있지 않다
그렇다면 arrow function에는 this가 없다고 했었던가?
아니다 arrow function은 자신보다 상위 컨텍스트에서 this를 할당받아 오는 것이다

예를 들어 객체 내부에 concise method로 정의된 함수가 있고 그리고 concise method안에 arrow function으로 작동하는 함수가 있다고 가정해보자
//예시
const obj = {
function (arr) {
arr.reduce((acc,cur) => acc+cur);
}
}
이때 concise method는 자신을 호출한 컨텍스트에서 this를 할당받는다
따라서 concise method 내부에 concise method가 위치하게 되면 내부에 있는 concise method는 window의 this를 할당받게 된다
이유는 내부의 함수는 obj에 영향을 받지 않고 외부에서 전달해준 값의 영향을 받는다 그렇기 때문에 window에 위치하게 되는 것이다
그렇다면 arrow function은 어떨까?
arrow function은 this를 상위 컨텍스트에서 할당받는다고 했으므로 concise method에서 this를 할당받아 object를 반환하게 되는 것이다
실행 컨텍스트란(execution context)
함수 실행 컨텍스트는 함수 호출 시 생성되는 컨텍스트이다
각각의 함수는 각각의 실행 컨텍스트를 갖는다
전역 실행 컨텍스트는 가장 기본이 되는 실행 컨텍스트로 하나의 프로그램에는 하나의 전역 실행 컨텍스트만이 존재할 수 있다 자바스크립트 코드가 실행될 때 생성된다
JS에서 함수는 일반 함수와 생성자 함수의 역할을 모두 수행할 수 있다
일반 함수
function a () {
return 'hello';
}
const hi = a();
위와 같이 작성한 코드는 일반 함수로써 역할을 하고 있다
이때 반환값은 문자열로 정상적으로 반환된다

생성자 함수
function a () {
return 'hello';
}
const hi = new a();
위와 같이 작성한 코드는 생성자 함수로써 역할을 하고 있다
이때 반환값은 객체가 된다

그렇다면 생성자 함수의 사용 예제를 하나 더 살펴보자
function Button() {
this.content = 'Download';
}
const a = new Button();
console.log(a); //Button:{content:'Download}
이때 this는 앞서 얘기했던 것처럼 동작하지 않는 것 처럼 보인다
이유는 생성자 함수로 사용했기 때문이다
생성자 함수에서는 함수를 통해 생성된 instance를 binding한다
즉 생성자 함수를 통해 생성된 a를 binding한다는 의미이고
a에는 {content : 'Download'}가 할당된다
또한 Button에 a가 binding되는 것이다
이는 함수를 객체로 취급하기에 가능한 일이다
만약 생성자 함수를 일반 함수처럼 사용하면 어떤 일이 생길까?
function Button(age) {
this.age = age;
}
const a = Button(10);
console.log(a); // undefined
console.log(window.age); // 10
a에 undefined가 할당되는 일이 발생한다
그렇다면 이 age는 어디로 간 것일까?
바로 전역 실행 컨텍스트로 간다
이유는 앞서 말했던 일반 함수의 this는 호출한 곳의 this를 할당받는다고 했고 지금 이 상황은 전역 실행 컨텍스트에서 호출했기에 추론할 수 있다
화살표 함수의 생성자 함수
화살표 함수는 생성자 함수를 내장하지 않는다
사실 일반함수 중에서도 concise method는 생성자 함수를 내장하지 않는다
'왜 이렇게 내장하고 있는게 적은가요?'
그래서 성능에 유리한 것이다
재귀란?
목표 작업 하나를 동일하면서 간단한 작업 여러개로 나눠서 작업할 수 있을때 유용한 프로그래밍 패턴이다
물론 단순화가 가능할때도 유용하다
이때 함수가 자기 자신을 호출하는 것을 재귀함수라고 부른다
하지만 재귀함수는 탈출구를 만들어놓지 않으면 영원히 갇혀버릴 수 있으니 주의해야한다
예제 코드를 보며 재귀를 이해해보자
function a() {
a();
}
function pow(x, n) {
if (n == 1) {
return x;
} else {
return x * pow(x, n - 1);
}
}
console.log(pow(2,3)); // 8
천천히 단계별로 살펴보자
재귀함수가 실행되는 것처럼 설명해보았다 이해가 되는가?
사실 처음에 이해하기 어려워 그림으로 그려보면서 이해해봤다

이때 재귀함수의 핵심은 함수가 실행될때 내부 함수가 실행되면 외부 함수는 작동을 정지한다는 것이다
재귀가 일어날 때 실행 컨텍스트가 계속해서 생성되고 이는 callstack에 쌓인다
이것은 구글 크롬에서 제공하는 breakpoints와 callstack으로 확인해보았다

참고로 아까 말했듯 이 실행 컨텍스트가 과도하게 생긴다면 callstack이 초과되어 오류가 발생한다
재귀의 흐름은 이렇다
현재 함수의 일시 정지정지된 함수와 연결된 실행 컨텍스트는 실행 컨텍스트 스택이라는 특별한 자료 구조에 저장중첩 호출 실행중첩 호출 실행이 종료되면 실행 컨텍스트 스택에 저장된 실행 컨텍스트를 불러와 정지된 함수의 실행을 이어나감스택이 계속해서 쌓이다보면 이는 반드시 성능 저하를 야기한다(메모리 문제)
그래서 스택에 쌓이지 않는 반복문이 당연히 좋다
하지만 코드의 가독성 혹은 유지보수 측면에서는 간결성을 강조한 재귀가 좋다
크롬에서는 stack에 쌓이는 것 말고도 현재 값에 대한 정보도 확인할 수 있다
예제 코드 몇 개와 실행을 그림으로 이해한 것들 추가로 보여주면서 memoization을 언급하며 재귀함수는 마무리 하겠다
function fibonacci(n) {
if (n <= 0) {
return 0;
} else if (n === 1) {
return 1;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}

이 함수는 중복된 계산이 존재한다 예를들어 왼쪽 부분에 있는 fibonacci(1), fibonacci(2) , fibonacci(3)은 오른쪽에서도 존재하지만 둘 다 따로 계산하게 된다
이때 실행 시간이 언급한 부분은 2배가 되는 것이다
이를 해결하기 위한 방법이 memoization이다
const memofibo = (n) => {
if (n <= 0) return 0;
if (n <= 2) return 1;
if (memofibo.cache[n]) {
return memofibo.cache[n];
} else {
return (memofibo.cache[n] = memofibo(n - 1) + memofibo(n - 2));
}
};
memofibo.cache = {};
우선 memoization을 언급하기 전에 함수부분에서 언급했던 '함수는 객체다'를 이용해 함수에 객체를 할당한 것이다
이는 객체를 따로 정의하지 않아도 된다는 장점이 존재한다
이제 memoization에 대해 살펴보자
우선 계산한 값들을 객체에 저장하고 만약 객체에서 계산한 흔적이 있다면 계산하지 않고 객체에서 참조하여 사용하는 것이다
이렇게 되면 아까 언급했던 문제가 해결된다
마지막으로 재귀함수의 다른 예제와 그림 설명을 첨부하며 다음 파트로 넘어가겠다
const SocialPartners = {
foundingDate: 2021,
team: {
marketing: [
{
name: '이진아',
salary: 3_250_000,
},
{
name: '박연성',
salary: 2_600_000,
},
],
design: {
ui: [
{
name: '송우진',
salary: 3_920_000,
},
{
name: '김지평',
salary: 2_743_000,
},
],
uxd: [
{
name: '이수아',
salary: 4_000_000,
},
{
name: '최철상',
salary: 3_208_000,
},
{
name: '고요미',
salary: 2_106_000,
},
],
},
},
};
function sumSalaries(department) {
if (Array.isArray(department)) {
return department.reduce((acc, cur) => acc + cur.salary, 0);
} else {
let sum = 0;
for (let sub of Object.values(department)) {
sum += sumSalaries(sub);
}
return sum;
}
}

객체 생성
객체는 생성자 문법과 객체 리터럴 문법을 사용해 생성할 수 있다
const obj1 = new Object(); // 생성자 문법
const obj2 = {}; // 객체 리터럴 문법
이때 obj1,obj2 모두 const를 사용해서 생성했다
하지만 const를 사용했다고 해서 객체 내부의 내용을 변경하지 못하는건 아니다
그렇기에 const를 상수변수라고 부른다
객체를 생성하는 과정 중에 마지막 property에는 , 는 생략이 가능하다
하지만 생략하지 않고 쓴다면 이를 trailing 혹은 hanging이라고 부른다
key
객체에 key에 접근하기 위해 점표기법을 사용할 수 있다
const obj = {
name:'black-skirt',
quality: 'very-good',
score: '5/5'
}
console.log(obj.name); // black-skirt
만약 객체 내부의 key값을 공백을 포함하여 작성하고 싶다면 " "로 묶어주면 된다
const obj = {
"band name":'black-skirt',
quality: 'very-good',
score: '5/5'
}
하지만 이렇게 작명을 하는 순간 점표기법으로는 해당 key에 접근이 불가능해진다
이는 대괄호 표기법이 그 자리를 대신해준다
const obj = {
"band name":'black-skirt',
quality: 'very-good',
score: '5/5'
}
console.log(obj."band name"); // 에러가 출력된다
console.log(obj["band name"]); // black-skirt

또한 객체의 key값에 변수를 주게 되면 이 또한 점표기법으로는 인식할 수 없다
따라서 이것도 역시 대괄호 표기법이 대신한다
const obj = {
"band name":'black-skirt',
quality: 'very-good',
score: '5/5'
}
const str = 'quality';
console.log(obj.str); // undefined
console.log(obj[str]); // very-good

계산된 property
let calculateProperty = 'phone'; // phone | tel
function createUser(name, age, phone) {
return {
name: name,
age: age,
[calculateProperty]: phone,
};
}
//이러면 phone:phone도 객체에 포함이 된다
이는 앞서 작성해놓았던 변수(calculateProperty)를 배열 내부에서 key값으로 지정해주는 것이 가능하다
단축 property
const student = {
name,
email,
authorization,
isLogin,
};
// name,email,authorization,isLogin은 이미 지정된 변수가 존재한다
원래대로라면 name:name와 같이 사용해야 되지만
key값과 같은 이름의 변수가 외부에 지정이 되어있다면 위와 같이 단축해서 표기할 수 있다
구조분해 할당은 코드를 보며 이해해보자
const arr = [10,100,1000,10_000];
const [ , a2 , a3] = arr;
이때 a2에는 100(a[1])이 a3에는 1000(a[2])이 할당된다
앞에 있는 빈 부분은 ',' 로 표시해 알려준다
여기서도 spread syntax 사용이 가능하다
const arr = [10,100,1000,10_000];
const [ , a2 , ...rest] = arr;
이때 a2에는 100이 rest에는 3번째와 4번째 값이 배열로 할당된다
그리고 주어진 배열보다 더 많은 index를 작성해도 오류는 일어나진 않는다
const arr = [10,100,1000,10_000];
const [a1,a2,a3,a4,a5] = arr;
이때 a1~a4에는 각각 10~10_000이 차례대로 할당된다
하지만 a5는 할당할 값이 없으므로 undefined를 할당한다
이때 a5도 값을 주고 싶다면 default value를 설정하는 것이 가능하다
const arr = [10,100,1000,10_000];
const [a1,a2,a3,a4,a5=100_000] = arr;
이렇게 되면 주어진 배열에는 할당할 값이 없지만 default value가 있으므로 a5에는 100,000이 할당된다
배열의 구조분해 할당에선 순서를 바꿀 수 없다