# ES2015 정리
# ES2015란?
ES2015란 자바스크립트의 버전으로 ES6라고도 불린다. 앞으로 매년 새로운 버전이 나올 예정이라고 하여 6, 7 같은 넘버링보다는 연도를 붙여 버전을 표기한다고 한다.
표준 자바스크립트의 버전은 ECMA라는 단체에서 발표되는데 버전 네이밍의 ES는 EcmaScript의 줄임말이다.
# const, let
자바스크립트 변수 선언 키워드였던 var
에서 const
와 let
이 추가되었다.
특성 | var | let | const |
---|---|---|---|
재할당 가능 여부 | 가능 | 가능 | 불가능 |
스코프 | 함수 스코프 | 블록 스코프 | 블록 스코프 |
호이스팅 (Hoisting) | 선언과 초기화가 동시에 됨 | 선언 후 초기화 전까지 사용 불가능 | 선언 후 초기화 전까지 사용 불가능 |
# 재할당
var
와 let
은 변수에 값을 수정할 수 있으며, const
는 수정할 수 없고, 선언 시 값을 무조간 할당해주어야 한다.
const x = 0;
x = 1; // TypeError: Assignment to constant variable.
let y = 0;
y = 1;
const z; // SyntaxError: Missing initializer in const declaration.
2
3
4
5
6
7
# 스코프
var
는 함수 스코프를 가지지만 const
와 let
은 블록 스코프를 갖는다.
블록 스코프는 변수나 함수가 코드 블록({}
) 내에서 정의되고 유효한 범위를 의미한다. if
for
while
등과 같은 제어문의 중괄호 안에 const
나 let
으로 변수나 함수를 선언하면 그 안에서만 접근할 수 있다.
그와 달리 var
의 경우 함수 스코프를 가지기 때문에 선언된 함수 내에서만 유효한 범위를 가지게 된다.
function example() {
// -- 함수 스코프
if (true) {
// -- 블록 스코프
var x = 1;
let y = 2;
const z = 3;
console.log(x); // 1
console.log(y); // 2
console.log(z); // 3
// -- 블록 스코프
}
console.log(x); // 1
console.log(y); // ReferenceError: y is not defined
console.log(z); // ReferenceError: z is not defined
// -- 함수 스코프
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 호이스팅
자바스크립트 엔진은 변수 선언을 포함한 모든 선언문을 런타임 이전 단계에서 먼저 실행한다.
변수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징을 변수 호이스팅이라 한다.
마찬가지로 함수 선언문에서 일어나는 호이스팅은 함수 호이스팅이라 한다. 단, 함수 표현식의 경우에는 함수가 아닌 변수 호이스팅이 일어난다.
이처럼 var
let
const
로 선언한 변수는 변수 호이스팅이 일어난다. 하지만 let
과 const
는 마치 호이스팅이 발생하지 않는 것처럼 동작한다.
console.log(x); // undefined
var x;
console.log(x); // undefined
2
3
4
5
var
키워드로 선언한 변수는 자바스크립트 엔진에 의해 암묵적으로 선언 단계와 초기화 단계가 한번에 진행된다. 따라서 변수 선언문 이전에 변수를 참조할 수 있다.
console.log(x); // ReferenceError: x is not defined
let x;
console.log(x); // undefined
x = 1;
console.log(x); // 1
2
3
4
5
6
7
let
키워드로 선언한 변수는 선언 단계와 초기화 단계가 분리되어 진행된다. 자바스크립트 엔진에 의해 선언 단계가 먼저 실행되지만 초기화 단계는 변수 선언문에 도달했을 때 실행되기 때문에 초기화 단계 이전에는 변수를 참조할 수 없다.
스코프의 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없는 구간을 일시적 사각지대(Temporal Dead Zone, TDZ) 라고 부른다.
const
키워드로 선언한 변수는 초기화가 되기 전까지 사용될 수 없으며 let
과 마찬가지로 변수 호이스팅이 발생하지 않는 것처럼 동작한다.
# 템플릿 리터럴
백틱(`)을 이용한 새로운 문자열 표기법
일반적인 문자열은 줄바꿈이 허용되지 않고, 공백을 표현하기 위해서는 백슬래시()로 시작하는 이스케이프 시퀀스를 사용해야 한다.
템플릿 리터럴은 여러 줄에 걸쳐 문자열을 작성할 수 있으며 템플릿 리터럴 내의 모든 공백(white-space)은 있는 그대로 적용된다.
const first = "Mihyun";
const last = "Lee";
// ES5: 문자열 연결
console.log("My name is " + first + " " + last + ".");
// "My name is Mihyun Lee."
// ES6: String Interpolation
console.log(`My name is ${first} ${last}.`);
// "My name is Mihyun Lee."
2
3
4
5
6
7
8
9
10
또한 + 연산자를 사용하지 않아도 ${...}
으로 표현식을 감싸 표현할 수 있으며, 이를 문자열 인터폴레이션(String Interpolation) 이라 한다.
문자열 인터폴레이션 내의 표현식은 문자열로 강제 타입 변환된다.
# 객체 리터럴
# ES6의 객체 리터럴 프로퍼티 기능 확장
프로퍼티 축약 표현
프로퍼티 값으로 변수를 사용하는 경우, 프로퍼티 이름을 생략할 수 있다. 이때 프로퍼티 이름은 변수의 이름으로 자동 생성된다.
프로퍼티 키 동적 생성
ES5에서 프로퍼티 키를 동적으로 생성할 때 객체 리터럴 외부에서 대괄호 표기법([...])을 사용했다면, ES6에서는 템플릿 리터럴과 함께 객체 리터럴 내부에서 프로퍼티 키를 동적으로 생성할 수 있다.
메소드 축약 표현
메소드를 선언할 때 function 키워드를 생략한 축약 표현을 사용할 수 있다.
__proto__
프로퍼티에 의한 상속ES5에서는 객체 리터럴을 상속하기 위해
Object.create()
함수를 사용한다. ES6에서는 객체 리터럴 내부에서__proto__
프로퍼티를 직접 설정할 수 있다. 이는 객체 리터럴에 의해 생성된 객체의__proto__
프로퍼티에 다른 객체를 직접 바인딩하여 상속을 표현한 것이다.
// ES5
var x = 1,
y = 2;
var prefix = "ES";
var obj = {
x: x,
y: y,
name: "Lee",
sayHi: function () {
console.log("Hi!" + this.name);
},
};
obj[prefix + 6] = "Fantastic";
// 프로토타입 패턴 상속
var childObj = Object.create(obj);
childObj.name = "Park";
obj.sayHi(); // Hi! Lee
childObj.sayHi(); // Hi! Park
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ES6
let x = 1,
y = 2;
const prefix = "ES";
const obj = {
x, // (1) 프로퍼티 축약 표현
y,
name: "Lee",
// (3) 메소드 축약 표현
sayHi() {
console.log("Hi!" + this.name);
},
// (2) 프로퍼티 키 동적 생성
[`${prefix}6`]: "Fantastic",
};
// (4) proto 프로퍼티에 의한 상속
const childObj = {
// __proto__ 프로퍼티에 다른 객체를 직접 바인딩 하여 상속을 구현한다.
__proto__: obj,
name: "Park",
};
obj.sayHi(); // Hi! Lee
childObj.sayHi(); // Hi! Park
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Arrow Function, 화살표 함수
화살표 함수는 function 키워드 대신 화살표를 사용하여 간단하게 함수를 선언할 수 있다.
// ES5
function add1(x, y) {
return x + y;
}
// ES6
const add2 = (x, y) => x + y;
2
3
4
5
6
7
화살표 함수를 호출하기 위해서는 함수 표현식을 사용하거나, 콜백 함수로 사용할 수 있다.
# function 키워드와의 차이점 - this
자바스크립트의 경우 함수 호출 방식에 의해 this에 바인딩할 객체가 동적으로 결정된다. 즉, 함수를 선언할 때가 아닌 호출할 때 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 결정된다.
화살표 함수의 경우 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정된다. 화살표 함수의 this는 언제나 상위 스코프의 this를 가리키며 이를 Lexical this라 한다.
화살표 함수의 this 바인딩 객체 결정 방식은 함수의 상위 스코프를 결정하는 방식인 렉시컬 스코프와 유사하다.
화살표 함수는 call, apply, bind 메소드를 사용하여 this를 변경할 수 없다.
# 화살표 함수 사용 시 주의할 점
메소드 정의
객체의 메소드를 정의할 때 화살표 함수로 정의하게 되면 메소드 내의 this는 메소드를 호출한 객체를 가리키지 않는다. 메소드를 호출한 객체가 아닌 상위 컨택스트인 전역 객체 window를 가리킨다.
화살표 함수로 정의된 메소드를 prototype에 할당
화살표 함수로 객체의 메소드를 정의하였을 때와 같은 문제가 발생한다.
// ❌ Bad
const person = {
name: "Lee",
};
Object.prototype.sayHi = () => console.log(`Hi ${this.name}`);
person.sayHi(); // Hi undefined
// 👍🏻 Good
const person = {
name: "Lee",
};
Object.prototype.sayHi = function () {
console.log(`Hi ${this.name}`);
};
person.sayHi(); // Hi Lee
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
생성자 함수
화살표 함수는 생성자 함수로 사용할 수 없다. 생성자 함수는 prototype 프로퍼티를 가지지만, 화살표 함수는 prototype 프로퍼티를 가지고 있지 않다.
addEventListener 함수의 콜백 함수
addEventListener 함수의 콜백 함수를 화살표 함수로 정의하면 this가 상위 컨택스트인 전역 객체 window를 가리킨다.
function 키워드로 정의한 함수를 사용하여야 this는 이벤트 리스너에 바인딩된 요소(currentTarget)을 가리킨다.
// ❌ Bad
var button = document.getElementById("myButton");
button.addEventListener("click", () => {
console.log(this === window); // => true
this.innerHTML = "Clicked button";
});
// 👍🏻 Good
var button = document.getElementById("myButton");
button.addEventListener("click", function () {
console.log(this === button); // => true
this.innerHTML = "Clicked button";
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Destructuring, 비구조화 할당
구조화된 배열 또는 객체를 Destructuring(비구조화, 파괴)하여 개별적인 변수에 할당한다.
// -- 배열 비구조화 할당
const arr = [1, 2, 3];
// 배열의 순서대로 변수에 할당
const [a, b, c] = arr;
console.log(a, b, c); // 1 2 3
// -- 객체 비구조화 할당
const obj = { x: 1, y: 2 };
// 프로퍼티 이름에 맞는 프로퍼티 값 할당
const { y, x } = obj;
console.log(x, y); // 1 2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Promise, 프로미스
기존의 비동기 처리를 위한 콜백 패턴은 콜백 헬로 인해 가독성이 나쁘고 비동기 처리 중 발생한 에러의 처치가 곤란하다.
이를 위해 ES6에서는 프로미스가 도입되었다.
// Promise 객체의 생성
const promise = new Promise((resolve, reject) => {
// 비동기 작업을 수행한다.
if (/* 비동기 작업 수행 성공 */) {
resolve('result');
}
else { /* 비동기 작업 수행 실패 */
reject('failure reason');
}
});
promise
.then((message) => {
console.log(message); // result
})
.catch((error) => {
console.log(error); // failure reason
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
resolve가 호출되면 then이 실행되고, reject가 호출되면 catch가 실행된다.
또한 Promise.all
을 활용하여 여러 개의 비동기 처리를 한꺼번에 할 수 있다.
# async/await
async/await는 ES2017(ES8)에 도입되어 Promise 로직을 더 쉽고 간결하게 사용할 수 있게 해준다. 단, async/await가 Promise를 대체하기 위한 기능은 아니다.
function 키워드 앞에 async
를 붙이고, 비동기로 처리되는 부분 앞에 await
를 붙여 사용한다.
await를 붙인 프로미스가 resolve 될 때까지 기다린 후 다음 로직으로 넘어가는 방식이다.
// 기존 Promise.then() 형식
function promise() {
delay(1000)
.then(() => {
return delay(2000);
})
.then(() => {
return Promise.resolve("끝");
})
.then((result) => {
console.log(result);
});
}
// async/await 방식
async function promise() {
await delay(1000);
await delay(2000);
const result = await Promise.resolve("끝");
console.log(result);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21