ES6문법은 ECMA-262 표준의 제 6판이며 ECMAScript 사양의 변경 사항을 명세합니다. ECMAScript는 매년 새롭게 발표되는데 ES6문법만 많이 거론되는 이유는 기존의 틀을 깨는 많은 개념들이 새로이 도입되었기 때문인데요. 어떤 변화가 있었는지 알아보는 시간을 갖도록 하겠습니다.
ES6문법 전에 Javascript에서 변수를 선언하기 위해서는 var 키워드를 사용하는 것이 유일하였습니다. var에는 아래와 같은 특징이 있습니다.
var 키워드로 선언한 변수는 대부분 전역변수로 생성됩니다. 전역변수는 사용에 있어서 편리하다는 장점이 있으나 유효 범위가 넓어서 프로그램이 복잡해지면 어디서 어떻게 사용되는지 파악하기가 힘들고 의도하지 않게 변경될 수도 있어 관리하기가 복잡하고 어려워집니다.
이런 문제들을 보완하기 위해 const와 let키워드가 도입되었습니다.
let piz = 123;
{
let piz = 234;
console.log(piz) // 234
}
console.log(piz) // 123
함수 레벨 스코프였다면 블록 안 piz와 블록 밖 piz는 같은 piz이므로 블록 안과 밖에 console은 마지막에 저장된 값인 234로 동일하게 출력되었을 것입니다.
블록 스코프내에서 동일한 이름을 갖는 변수를 중복해서 선언할 수 없습니다.
let janna = 123;
let janna = 234; // Uncaught SyntaxError: Identifier 'janna' has already been declared
console.log(ryze) // ReferenceError: Cannot access 'ryze' before initialization
let ryze;
let은 재할당이 자유롭지만 const는 재할당이 금지되어 있습니다. 따라서 상수(변하지 않는 값)은 const로 생성하고 유동적인 값은 let으로 선언하여 사용해야 합니다. 또한 const는 선언과 동시에 초기화도 이루어져야 합니다.
let garen = 123;
garen = 345;
const rux = 234;
rux = 345; // TypeError: Assignment to constant variable.
let과 const의 특징을 살펴보면 기존의 var 키워드로 선언했을 때 전역변수의 문제를 초래하던 특징들을 제한하고 보완하여 되도록 지역 변수를 활용하도록 유도한다는 것을 알 수 있습니다. 물론 let과 const는 비교적 최근에 도입되었고 그 이전 훨씬 많은 시간동안 var 키워드 하나로도 충분히 복잡하고 어려운 프로그램을 만들었습니다. 그러므로 그냥 선택의 폭이 넓어졌다고 생각하고 용도에 맞게 사용하시면 됩니다.
화살표 함수는 function 키워드 대신 화살표(=>)를 사용하여 함수를 선언할 수 있습니다.
일단 기본적으로 화살표 함수는 익명 함수로만 사용이 가능하여 호출하기 위해서는 표현식을 사용해야합니다.
// 매개변수 지정 할 떄
() => {...} // 매개변수가 없을 때
a => {...} // 매개변수가 하나 일 때에는 소괄호 생략이 가능하다.
(a,b) => {...} // 매개변수가 두 개 이상일 때에는 소괄호를 포함해야 한다.
//함수 선언 방법
const func = () => {
const garen ='데마시아를 위하여!';
return garen;
}; // 화살표 함수 기본 표현 방법
const func = x => x*x; // 한 라인로 표현할 경우 return 및 중괄호를 생략해도 알아서 해당 라인을 리턴해준다
템플릿 리터럴은 일반 문자열을 작성할 때 쓰는 ' 또는 " 같은 따옴표 대신에 백틱 ` 를 사용하는 새로운 문자열 표기법이다.
const leesin = `주먹이 곧 나의 무기요,
난 속죄할 것이오,
어디로 가야 하오,
지피지기면 백전백승,
백문이 불여일견`;
const name = 'ezreal';
// 일반적인 사용 방식
console.log("My name is" + name) // 출력: My name is ezreal
//템플릿 리터럴
console.log(`My name is ${name}`} // 출력: My name is ezreal
배열이나 문자열 또는 객체를 해체하여 그 요소를 반환해줍니다. 사용법은 원본의 접두어에 점 세개(...)을 붙여 주면 됩니다. 대신 조건이 있는데 해체하려는 대상이 Iterable한 객체 즉 Iterlator이어야 합니다.
// 문자열 전개
const str = "strong";
const arr = [...str];
console.log(arr) // 출력 : [ 's', 't', 'r', 'o', 'n', 'g' ]
// 배열 전개
const character = ['garen', 'ezreal', 'nami'];
const character2 = ['zac', 'vi'];
const arr1 = [...character];
const arr2 = [...character, ...character2];
console.log(arr1) // 출력 : ['garen', 'ezreal', 'nami']
console.log(arr2) // 출력 : ['garen', 'ezreal', 'nami', 'zac', 'vi']
//객체 전개
const character = {vi: 'vi', zac: 'zac'};
const character2 = {jax: 'jax'};
const obj1 = {...character};
const obj2 = {...character, ...character2};
console.log(obj1) // 출력 : {vi: 'vi', zac: 'zac'}
console.log(obj2) // 출력 : {vi: 'vi', zac: 'zac', jax: 'jax'}
비구조화 할당은 배열이나 객체의 속성을 해체하여 그 값을 변수에 담을 수 있도록 해주는 표현식입니다.
// 기본 변수 할당
const character = ['garen', 'ezreal', 'nami'];
const [garen, ezreal, nami] = character;
console.log(garen) // 출력 : garen
console.log(ezreal) // 출력 : ezreal
// 선언과 분리하여 할당
const jax, nami;
[jax, nami] = ['jax', 'nami'];
console.log(jax) // 출력 : jax
console.log(nami) // 출력 : nami
// 변수간의 값 교환
const zed = 'zed';
const pizz = 'pizz';
[zed, pizz] = [pizz, zed];
console.log(zed) // 출력 : pizz
console.log(pizz) // 출력 : zed
// 일부 값 무시하기
const character = ['garen', 'ezreal', 'nami'];
const [garen, ,nami] = character;
console.log(garen) // 출력 : garen
console.log(nami) // 출력 : nami
// 변수에 배열의 나머지 값 할당하기
const character = ['garen', 'ezreal', 'nami'];
const [garen, ...rest] = character;
console.log(garen) // 출력 : garen
console.log(nami) // 출력 : ['ezreal', 'nami']
// 기본 변수 할당
const character = {vi: 'vi', zac: 'zac'};
const {vi, zac} = character;
console.log(vi) // 출력 : vi
console.log(zac) // 출력 : zac
// 선언과 분리하여 할당
const vi, zac;
{vi, zac} = {vi: 'vi', zac: 'zac'};
// 새로운 변수 이름으로 할당
const character = {vi: 'vi', zac: 'zac'};
const {vi: cha1, zac: cha2} = character;
console.log(cha1) // 출력 : vi
console.log(cha2) // 출력 : zac
// for of 반복문에서 구조 분해
const character = {
pizz : 'pizz',
garen: {
speech : '데마시아를 위하여!'
},
ezreal: 'ezreal'
}
for(const {pizz: n, garen: {speech: s}} of character){
console.log(n, s) // 출력: pizz 데마시아를 위하여!
}
// 객체에 남은 속성 값 할당
const {garen, ...rest} = {garen: 'garen', ezreal: 'ezreal', nami: 'nami'};
console.log(garen) // 출력 : garen
console.log(rest) // 출력 : {ezreal: 'ezreal', nami: 'nami'}
프로미스는 비동기 처리를 위한 하나의 패턴입니다. 도입되기전에는 자바스크립트에서 비동기 처리를 하기위해서 콜백 패턴을 사용했습니다. 하지만 콜백 패턴에는 아래와 같은 단점이 있습니다.
특정 비동기 작업을 수행하고 그 결과를 토대로 다음 작업을 수행할 때 콜백 패턴을 사용하면 처리 순서를 보장하기 위해 여러 개의 콜백 함수가 중첩되어 가독성을 해치고 복장성이 높아지는 콜백 헬이 발생합니다.
아래는 콜백 헬의 대표적인 예이다.
callback1(function(val1){
callback2(function(val2){
callback3(function(val3){
callback4(function(val4){
callback5(function(val5){
...
})
})
})
})
})
콜백 패턴에서 비동기 처리를 할 때 에러를 처리하는데 곤란함을 겪습니다. 아래의 코드를 통해 설명하겠습니다.
try {
setTimeout(()=>{
throw new Error('에러가 발생했다!!');
}, 1000);
} catch (error) {
console.log(e) ;
}
try구문에서 setTimeout을 실행하면 setTimeout함수의 콜백 함수에서 1초뒤에 에러를 발생시킵니다. 허나 이 콜백 함수는 비동기이므로 테스크 큐에 있다가 콜 스택이 비워지면 이동해 실행되면서 에러가 발생합니다. 에러는 호출자로 전파되어야 하는데 그 시점에 호출자인 setTimeout의 컨텍스트는 이미 콜 스택에서 제거되어 없기 때문에 에러는 전파되지 못하고 잡아내지 못한채로 모든 프로세스가 종료됩니다.
이러한 문제를 극복하기 위해 프로미스(Promise)가 도입되었습니다.
프로미스는 기본적으로 Promise 생성자 함수를 통해 생성합니다. 이 함수는 비동기 작업을 수행할 때 쓰이는 콜백 함수(resolve, reject)를 인자로 받습니다.
const promise = new Promise((resolve, reject)=>{
// 비동기 작업을 작성
...
if(// 비동기 적업 성공 조건 작성)
// 비동기 작업 수행 성공 시 resolve 콜백 함수로 값 전달
resolve'비동기 작업 성공!!');
} else {
// 비동기 작업 수행이 실패할 시 reject 콜백 함수로 값 전달
reject('비동기 작업 실패!!');
}
});
비동기 작업을 수행하고 그 결과를 토대로 다른 작업을 수행 할 때 프로미스가 있기 전에는 콜백 패턴으로 사용했고 이는 콜백 헬이라는 문제를 야기한다고 설명했습니다. 이에 프로미스는 then()과 catch()라는 후속 처리 메소드를 통해 위의 해결하려 접근하였습니다. then 함수는 ajax를 수행하고 일반적인 후속 작업을 할 때 사용되고 catch는 예외 처리를 위한 함수입니다. catch는 ajax에서의 에러뿐 만이 아니라 then에서 수행하는 작업에서의 에러도 잡아냅니다. 이해를 위해 아래의 코드를 살펴봅시다.
// 비동기 xhr ajax 코드
const promiseAjax = (method, url) => {
return new Promise((resolve, reject)=>{
// 비동기 작업을 작성
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.onreadystatechange = function () {
// 서버 응답하지 않으면 바로 종료
if (xhr.readyState !== XMLHttpRequest.DONE) return;
// 비동기 작업 수행 성공 조건문
if (xhr.status >= 200 && xhr.status < 400) {
// 비동기 작업 수행 성공 시 resolve 콜백 함수로 값 전달
resolve(xhr.response);
} else {
// 비동기 작업 수행이 실패할 시 reject 콜백 함수로 값 전달
reject(new Error(xhr.status));
}
};
});
}
// ajax 수행 후 후속처리
promiseAjax('GET', 'http://example.com')
.then(res1 => console.log(res))
// then 함수를 체이닝을 통해 계속해서 작성할 수 있다.
// .then(res2 => ...)
// .then(res3 => ...)
.catch(err => console.error(err)); // 에러 처리
분명 프로미스를 통해 콜백의 단점으로 지적되는 부분이 많이 개선되었습니다. 허나 then()함수를 체이닝하여 꼬리가 계속 길어지는게 가독성 측면에서 좋아 보이지 않고 에러를 처리할 때 catch에서 모든 에러를 잡아내다 보니까 어디서 발생한 에러인지 명확히 확인하는데 어려움이 있다는 단점이 있습니다.