[번역] Overview of JavaScript ES6 features (a.k.a ECMAScript 6 and ES2015+)

[번역] Overview of JavaScript ES6 features (a.k.a ECMAScript 6 and ES2015+)

이 글은 Adrian MejiaOverview of JavaScript ES6 features (a.k.a ECMAScript 6 and ES2015+)을 번역한 글입니다. 오타나 오역 제보는 언제나 환영입니다!


자바스크립트는 지난 몇 년간 꽤 많은 변화가 있었습니다. 지금 당장 쓸 수 있는 새로운 12가지 기능은 다음과 같습니다.

1. JavaScript History

이번에 자바스크립트에 추가된 기능을 ECMAScript 6 이라고 부릅니다. ES6 나 ES2015+ 라고도 불리죠.

1995년 자바스크립트 개념이 도입된 이래로, 자바스크립트는 서서히 진화해 오고 있었습니다. 기능을 추가하는 작업은 몇 년에 한번씩 일어나곤 했습니다. ECMAScript는 자바스크립트의 이러한 방향을 바로 잡기 위해 1997년에 등장했습니다. ECMAScript는 ES3, ES5, ES6 등과 같은 버젼을 발표해 왔습니다.

보시다시피, ES3, ES5 와 ES6은 각각 10년과 6년의 간격이 있습니다. 새 모델은 매년 작고 점진적인 변화를 만드는 것입니다. ES6 처럼 한 번에 엄청난 변화를 하는것 대신에요.

2. Browsers Support

모든 현대 브라우저와 환경은 ES6를 이미 지원하고 있습니다!


source: https://kangax.github.io/compat-table/es6/

Chrome, MS Edge, Firefox, Safari, Node와 많은 다른 환경에서 이미 자바스크립트 ES6의 기본적인 기능을 지원합니다. 그래서 당신이 이제 배울 모든 튜토리얼은 당장 사용해 볼 수 있습니다.

ECMAScript 6를 시작해봅시다!

3. Core ES6 Features

브라우저 콘솔에서 모든 코드들을 테스트 할 수 있습니다.

그러니 글만 읽지 말고 ES5와 ES6 예제를 테스트 해보길 바랍니다. 어서 해보죠 💪

3.1 Block scope variables

ES6에서 변수를 선언하기 위해 var 대신 let/const를 사용합니다.

var에 무슨 문제가 있었을까요?

var의 문제는 for문이나 if문과 같은 코드 블록에서 변수가 누출되는 것입니다.

ES5

1
2
3
4
5
6
7
8
9
10
var x = 'outer';
function test(inner) {
if (inner) {
var x = 'inner'; // scope whole function
return x;
}
return x; // gets redefined because line 4 declaration is hoisted
}
test(false); // undefined 😱
test(true); // inner

test(false)에서 outer가 반환되길 기대했지만, undefined를 얻었습니다.

왜 일까요?

왜냐하면 if 블록이 실행되지 않더라도 네째 줄의 var x가 호이스트(hoisted)되기 때문입니다.

var 호이스팅:

  • var는 함수 범위(scope)입니다. 이것은 선언되기 전에도 전체 함수에서 사용할 수 있습니다.
  • 선언(Declarations)은 호이스트(Hoisted)됩니다. 그래서 선언되기 전에도 변수를 사용할 수 있습니다.
  • 초기화(Initializations)는 호이스트되지 않습니다. 만약 var를 사용한다면 항상 맨 위에 변수를 선언하세요.
  • 호이스팅 규칙을 적용해 본 후에 무슨 일이 일어났는지 더 잘 이해할 수 있습니다.
    ES5
1
2
3
4
5
6
7
8
9
10
> var x = 'outer';
> function test(inner) {
> var x; // HOISTED DECLARATION
> if (inner) {
> x = 'inner'; // INITIALIZATION NOT HOISTED
> return x;
> }
> return x;
> }
>

ECMAScript 2015가 도와주러 왔습니다:

ES6

1
2
3
4
5
6
7
8
9
10
let x = 'outer';
function test(inner) {
if (inner) {
let x = 'inner';
return x;
}
return x; // gets result from line 1 as expected
}
test(false); // outer
test(true); // inner

varlet으로 변경하면 예상대로 작동합니다. if 블록이 호출되지 않는다면, 변수 x는 블록 밖으로 호이스트되지 않습니다.

호이스팅(hoisting) 과 “temporal dead zone”

  • ES6에서 let은 블록의 맨 위로 호이스트 됩니다.(ES5 처럼 함수의 맨위가 아닌)
  • 그러나, 변수가 선언되기 전에 블록에서 변수를 참조하는 것은 ReferenceError를 초래합니다.
  • let은 차단된 스코프를 가집니다. 선언되기 전에 사용할 수 없습니다.
  • “Temporal dead zone”은 블락의 처음부터 변수가 선언되기 전까지의 영역입니다.

IIFE

IIFE를 설명하기 전에 예제를 봅시다. 여길 봐주세요:

ES5

1
2
3
4
{
var private = 1;
}
console.log(private); // 1

보시다시피, private은 유출됩니다. 그것을 감싸기 위해 IIFE (immediately-invoked function expression)을 사용할 필요가 있습니다:

ES5

1
2
3
4
(function(){
var private2 = 1;
})();
console.log(private2); // Uncaught ReferenceError

jQuery/lodash 나 다른 오픈소스 프로젝트를 살펴보면 그것들은 전역 환경의 오염을 피하기위해 IIFE를 사용하고, 단지 _, $jQuery만 전역으로 정의합니다.

ES6에서 더 깔끔합니다. 블록과 let을 사용하면 더이상 IIFE를 사용할 필요가 없습니다.

ES6

1
2
3
4
{
let private3 = 1;
}
console.log(private3); // Uncaught ReferenceError

Const

변수를 전혀 변경하지 않는다면 const를 사용할 수 있습니다.

밑줄: var를 버리고 letconst로.

  • 모든 참조에 var의 사용을 피하고 const를 사용하세요.
  • 만약 참조를 재 정의해야 한다면 const 대신 let을 사용하세요

3.2 Template Literals

템플릿 리터럴을 사용하면 더이상 중첩된 연결을 하지 않아도 됩니다. 여길 보세요:

ES5

1
2
3
var first = 'Adrian';
var last = 'Mejia';
console.log('Your name is ' + first + ' ' + last + '.');

이제 우린 역 따옴표( ` )와 문자열 보간 ${}을 사용할 수 있습니다:

ES6

1
2
3
const first = 'Adrian';
const last = 'Mejia';
console.log(`Your name is ${first} ${last}.`);

3.3 Multi-line strings

아래처럼 더이상 문자열과 \n을 연결하지 않아도 됩니다.

ES5

1
2
3
4
5
6
7
8
9
var template = '<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >\n' +
' <div class="view">\n' +
' <input class="toggle" type="checkbox" [checked]="todo.isDone">\n' +
' <label></label>\n' +
' <button class="destroy"></button>\n' +
' </div>\n' +
' <input class="edit" value="">\n' +
'</li>';
console.log(template);

ES6에서 역 따옴표를 사용하여 이를 해결할 수 있습니다.

ES6

1
2
3
4
5
6
7
8
9
const template = `<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >
<div class="view">
<input class="toggle" type="checkbox" [checked]="todo.isDone">
<label></label>
<button class="destroy"></button>
</div>
<input class="edit" value="">
</li>`;
console.log(template);

두 코드는 정확히 똑같은 결과를 가지게 됩니다.

3.4 Destructuring Assignment

ES6 비구조화(Destructuring)은 매우 유용하고 중요합니다. 아래 예제를 보세요:

배열에서 요소 가져오기

ES5

1
2
3
4
var array = [1, 2, 3, 4];
var first = array[0];
var third = array[2];
console.log(first, third); // 1 3

다음과 같습니다:

ES6

1
2
3
const array = [1, 2, 3, 4];
const [first, ,third] = array;
console.log(first, third); // 1 3

값 치환

ES5

1
2
3
4
5
6
var a = 1;
var b = 2;
var tmp = a;
a = b;
b = tmp;
console.log(a, b); // 2 1

다음과 같습니다:

ES6

1
2
3
4
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1

여러 반환 값에 대한 비구조화

ES5

1
2
3
4
5
6
7
8
function margin() {
var left=1, right=2, top=3, bottom=4;
return { left: left, right: right, top: top, bottom: bottom };
}
var data = margin();
var left = data.left;
var bottom = data.bottom;
console.log(left, bottom); // 1 4

세째 줄에서, 다음과 같은 배열로 리턴할 수도 있습니다.

1
return [left, right, top, bottom];

하지만 호출자(caller)는 반환된 데이터의 순서에 대해 생각해볼 필요가 있습니다.

1
2
var left = data[0];
var bottom = data[3];

ES6에서, 호출자는 필요한 데이터만 설택할 수 있습니다. (여섯째 줄):

ES6

1
2
3
4
5
6
function margin() {
const left=1, right=2, top=3, bottom=4;
return { left, right, top, bottom };
}
const { left, bottom } = margin();
console.log(left, bottom); // 1 4

Notice: 셋째 줄에서, 우리는 ES6의 새로운 기능을 사용하고 있습니다. { left: left }{ left }로 줄일 수 있습니다. ES5 버젼과 비교하면 얼마나 간결한지 보이시죠. 멋지지 않나요?

일치하는 매개변수에 대한 비구조화

ES5

1
2
3
4
5
6
7
var user = {firstName: 'Adrian', lastName: 'Mejia'};
function getFullName(user) {
var firstName = user.firstName;
var lastName = user.lastName;
return firstName + ' ' + lastName;
}
console.log(getFullName(user)); // Adrian Mejia

다음과 같습니다.(하지만 더 간결한)

ES6

1
2
3
4
5
const user = {firstName: 'Adrian', lastName: 'Mejia'};
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
console.log(getFullName(user)); // Adrian Mejia

Deep Matching

ES5

1
2
3
4
5
6
7
function settings() {
return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
}
var tmp = settings();
var displayColor = tmp.display.color;
var keyboardLayout = tmp.keyboard.layout;
console.log(displayColor, keyboardLayout); // red querty

다음과 같습니다.(하지만 더 간결한)

ES6

1
2
3
4
5
function settings() {
return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
}
const { display: { color: displayColor }, keyboard: { layout: keyboardLayout }} = settings();
console.log(displayColor, keyboardLayout); // red querty

이것은 객체 비구조화라고도 불립니다.

보시다시피, 비구조화는 매우 유용하고 좋은 코딩 스타일을 가지게 합니다.

모범 사례:

  • 배열 비구조화를 사용하여 요소를 가져오거나 값을 바꾸길 바랍니다. 임시 참조를 만들지 않아도 되게 도와줍니다.
  • 여러 값을 반환할 때 배열 비구조화를 사용하는 대신 객체 비구조화를 사용하세요.

3.5 Classes and Objects

ECMAScript 6에서, “constructor functions” 🔨 대신 “classes” 🍸 를 사용합니다.

자바스크립트에서 모든 단일 객체는 또 다른 객체인 프로토타입을 가지고 있습니다. 모든 자바스크립트 객체는 그들의 프로토타입의 메서드와 프로퍼티를 상속받습니다.

ES5에서, 우리는 다음과 같이 객체를 생성하기 위해 생성자 함수를 사용하여 객체 지향 프로그래밍(OOP)를 했습니다.

ES5

1
2
3
4
5
6
7
8
9
10
11
var Animal = (function () {
function MyConstructor(name) {
this.name = name;
}
MyConstructor.prototype.speak = function speak() {
console.log(this.name + ' makes a noise.');
};
return MyConstructor;
})();
var animal = new Animal('animal');
animal.speak(); // animal makes a noise.

ES6에는 몇가지 달콤한 문법이 존재합니다. 우리는 적은 보일러 플레이트와 새로운 키워드인 classconstructor 를 사용해 같은 작업을 수행할 수 있습니다. 또한 우리가 constructor.prototype.speak = function ()speak() 메서드를 얼마나 명확하게 정의했는지 확인해보세요:

ES6

1
2
3
4
5
6
7
8
9
10
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
const animal = new Animal('animal');
animal.speak(); // animal makes a noise.

보시다시피, 두 스타일 (ES5/6)은 동일한 결과를 만들어 내고 같은 방식으로 사용됩니다.

모범 사례:

  • 항상 클래스 문법을 사용하고 프로토 타입을 직접 조작하는 것을 피하세요. 왜냐구요? 왜냐하면 코드가 더 간결해지고 이해하기도 더 쉬워지기 때문입니다.
  • 빈 생성자를 가지는 것을 피하세요. 클래스는 생성자가 지정되지 않았다면 기본 생성자를 가집니다.

3.6 Inheritance

이전 Animal클래스를 기반으로 진행하겠습니다. 우리는 그것을 확장하여 Lion 클래스를 정의하기를 원합니다.

ES5에서, 이것은 프로토 타입 상속과 조금 더 관련이 있습니다.

ES5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Lion = (function () {
function MyConstructor(name){
Animal.call(this, name);
}
// prototypal inheritance
MyConstructor.prototype = Object.create(Animal.prototype);
MyConstructor.prototype.constructor = Animal;
MyConstructor.prototype.speak = function speak() {
Animal.prototype.speak.call(this);
console.log(this.name + ' roars 🦁');
};
return MyConstructor;
})();
var lion = new Lion('Simba');
lion.speak(); // Simba makes a noise.
// Simba roars.

모든 디테일한 사항들을 검토하지 않을겁니다:

  • 셋째 줄에서 우리는 Animal 생성자를 매개변수와 함께 명시적으로 호출합니다.
  • 7-8 줄에서 Lion 프로토 타입을 Animal의 프로토 타입으로 지정했습니다.
  • 11 줄에서 부모 클래스인 Animal에서 speak 메서드를 호출합니다.

ES6에서 새 키워드인 extendssuper가 있습니다.

ES6

1
2
3
4
5
6
7
8
9
class Lion extends Animal {
speak() {
super.speak();
console.log(this.name + ' roars 🦁');
}
}
const lion = new Lion('Simba');
lion.speak(); // Simba makes a noise.
// Simba roars.

이 ES6 코드가 같은 동작을 하는 ES5 코드와 비해 얼마나 읽기 쉬운지 보세요. 승리!

모범 사례:

  • 상속을 위해 내장되어 있는 extends를 사용하세요

3.7 Native Promises

콜백 헬(Callback Hell) 👹 대신 프러미스(Promise) 🙏 를 사용합니다.

ES5

1
2
3
4
5
6
7
8
9
10
11
12
function printAfterTimeout(string, timeout, done){
setTimeout(function(){
done(string);
}, timeout);
}
printAfterTimeout('Hello ', 2e3, function(result){
console.log(result);
// nested callback
printAfterTimeout(result + 'Reader', 2e3, function(result){
console.log(result);
});
});

우리는 done일 때 실행하기 위해 콜백을 받는 함수를 가지고 있습니다. 우리는 두번씩 차례대로 실행해야 합니다. 그것이 콜백에서 printAfterTimeout를 두 번째로 호출한 이유입니다.

이것은 세 번째나 네 번째 콜백이 필요할 경우 매우 빨리 동작하게 됩니다. promise가 어떻게 동작하는지 봅시다:

ES6

1
2
3
4
5
6
7
8
9
10
11
12
13
function printAfterTimeout(string, timeout){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(string);
}, timeout);
});
}
printAfterTimeout('Hello ', 2e3).then((result) => {
console.log(result);
return printAfterTimeout(result + 'Reader', 2e3);
}).then((result) => {
console.log(result);
});

보시다시피, promise를 사용하면 다른 함수가 완료된 후에 무언가를 하기 위해 then을 사용할 수 있습니다. 더이상 중첩 함수를 사용할 필요가 없어집니다.

3.8 Arrow functions

ES6는 함수 표현식을 제거하지 않았지만, 화살표 함수(arrow functions)라고 불리는 것을 새로 추가했습니다.

ES5에서 this의 문제가 몇가지 있었습니다:

ES5

1
2
3
4
5
6
7
var _this = this; // need to hold a reference
$('.btn').click(function(event){
_this.sendData(); // reference outer this
});
$('.input').on('change',function(event){
this.sendData(); // reference outer this
}.bind(this)); // bind to outer this

함수를 내부에서 참조하거나 bind를 사용하기 위해 임시로 this를 사용해야만 합니다. 하지만 ES6에서 화살표 함수를 사용할 수 있습니다.

ES6

1
2
3
4
5
// this will reference the outer one
$('.btn').click((event) => this.sendData());
// implicit returns
const ids = [291, 288, 984];
const messages = ids.map(value => `ID is ${value}`);

3.9 For…of

forforEach 대신 for...of를 사용합니다.

ES5

1
2
3
4
5
6
7
8
9
10
// for
var array = ['a', 'b', 'c', 'd'];
for (var i = 0; i < array.length; i++) {
var element = array[i];
console.log(element);
}
// forEach
array.forEach(function (element) {
console.log(element);
});

ES6 for...of 또한 반복 작업을 수행할 수 있습니다.

ES6

1
2
3
4
5
// for ...of
const array = ['a', 'b', 'c', 'd'];
for (const element of array) {
console.log(element);
}

3.10 Default parameters

변수가 정의되었는지 확인하는 것 대신 default parameters로 값을 지정할 수 있습니다. 전에 이렇게 한 적이 있나요?

ES5

1
2
3
4
5
6
7
8
9
10
function point(x, y, isFlag){
x = x || 0;
y = y || -1;
isFlag = isFlag || true;
console.log(x,y, isFlag);
}
point(0, 0) // 0 -1 true 😱
point(0, 0, false) // 0 -1 true 😱😱
point(1) // 1 -1 true
point() // 0 -1 true

아마도 변수가 값을 가지고 있는지 확인하거나 기본 값을 할당하는 일반적인 패턴입니다. 그러나 몇 가지 문제가 있는 것을 확인하세요:

  • 여덟째 줄, 0, 0을 넘겨주고 0, -1을 받게됩니다.
  • 아홉째 줄, false를 넘겨주고 true를 받게됩니다.

기본 매개변수로 부울 값을 갖거나 값을 0으로 설정한다면 작동하지 않습니다. 왜 그런지 아시나요? ES6 예를 들어 설명해 드리겠습니다. ;)

ES6를 사용하면 더 적은 코드로 더 좋게 할 수 있습니다!

ES6

1
2
3
4
5
6
7
function point(x = 0, y = -1, isFlag = true){
console.log(x,y, isFlag);
}
point(0, 0) // 0 0 true
point(0, 0, false) // 0 0 false
point(1) // 1 -1 true
point() // 0 -1 true

다섯째 줄과 여섯째 줄은 예상대로 결과를 얻었습니다. ES5 예제는 동작하지 않았었죠. 우리는 false, null, undefined0 은 거짓 값이므로 undefined를 먼저 확인해야 합니다. 숫자는 잊을 수 있습니다.

ES5

1
2
3
4
5
6
7
8
9
10
function point(x, y, isFlag){
x = x || 0;
y = typeof(y) === 'undefined' ? -1 : y;
isFlag = typeof(isFlag) === 'undefined' ? true : isFlag;
console.log(x,y, isFlag);
}
point(0, 0) // 0 0 true
point(0, 0, false) // 0 0 false
point(1) // 1 -1 true
point() // 0 -1 true

undefined를 확인할 때 비로소 예상대로 동작하게 됩니다.

3.11 Rest parameters

매개변수에서 나머지 매개변수와 전개 연산자로 왔습니다.

ES5에서 매개변수에서 임의의 변수를 얻는 것은 어렵습니다.

ES5

1
2
3
4
5
6
function printf(format) {
var params = [].slice.call(arguments, 1);
console.log('params: ', params);
console.log('format: ', format);
}
printf('%s %d %.2f', 'adrian', 321, Math.PI);

우리는 나머지 매개변수 ...를 사용하여 동일한 작업을 수행할 수 있습니다.

ES6

1
2
3
4
5
function printf(format, ...params) {
console.log('params: ', params);
console.log('format: ', format);
}
printf('%s %d %.2f', 'adrian', 321, Math.PI);

3.12 Spread operator

우리는 apply()대신 전개 연산자를 사용합니다. 우리는 다시 ...의 도움을 받습니다:

주의사항: 우리는 배열을 전달인자의 목록으로 바꾸기 위해 apply()를 사용합니다. 예를 들어, Math.max()는 전달인자의 목록을 취하지만 배열인 경우 apply를 사용하여 동작하게 합니다.

먼저 보았듯이, apply를 사용해 전달인자의 목록을 배열로 넘겼습니다.

ES5

1
Math.max.apply(Math, [2,100,1,6,43]) // 100

ES6에서는 전개 연산자를 사용할 수 있습니다.

ES6

1
Math.max(...[2,100,1,6,43]) // 100

또한 우리는 concat 대신 전개 연산자를 사용할 수 있습니다.

ES5

1
2
3
4
var array1 = [2,100,1,6,43];
var array2 = ['a', 'b', 'c', 'd'];
var array3 = [false, true, null, undefined];
console.log(array1.concat(array2, array3));

ES6에서 전개 연산자를 사용해 중첩된 배열을 병합 할 수 있습니다:

ES6

1
2
3
4
const array1 = [2,100,1,6,43];
const array2 = ['a', 'b', 'c', 'd'];
const array3 = [false, true, null, undefined];
console.log([...array1, ...array2, ...array3]);

4. Conclusion

자바스크립트는 많은 변화를 겪었습니다. 이 글은 모든 자바스크립트 개발자가 알아야 할 중심 기능을 다뤘습니다. 또한 코드를 보다 간결하고 쉽게 이해할 수 있는 모범 사례를 제공했습니다.

만약 꼭 알아야할 다른 기능이 있다면 아래 댓글로 알려주세요. 저는 이 글을 업데이트할 것 입니다.

5. 역자 후기

매번 ES6를 공부 해야지 생각만 하다가 좋은 글을 발견하여 읽게되었다.
읽는 도중에 영어가 막혀서 한글로 번역해서 보고 싶다고 생각했다.

그것이 나의 첫 번역이었다.

역시 영어가 서툰 나에게는 글 내용 자체를 이해하기도 힘들었다.
그래서 번역을 할 때 단어가 의미하는 것이 정확이 무엇인지 조사하고 그에 맞는 적절한 단어를 선택할 수 밖에 없었는데, 그 점이 오히려 나의 이해를 도운것 같다.

영어 공부를 더 해야겠다…

[원문보기]

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×