ECMAScript 2015 편집하기


편집하면 당신의 IP 주소가 공개적으로 기록됩니다. 계정을 만들고 로그인하면 편집 시 사용자 이름만 보이며, 위키 이용에 여러 가지 편의가 주어집니다.

편집을 취소할 수 있습니다. 이 편집을 되돌리려면 아래의 바뀐 내용을 확인한 후 게시해주세요.

최신판 당신의 편집
11번째 줄: 11번째 줄:
== 추가 및 변경점 ==
== 추가 및 변경점 ==
=== let, const ===
=== let, const ===
ES6 이전에는 변수를 선언하기 위해 <code>var</code>키워드를 사용했다. 기존 <code>var</code>로 선언한 변수는 함수 스코프를 가지고 있었지만 <code>let</code>과 <code>const</code>로 선언한 변수나 상수는 블록 스코프를 가진다. 즉 중괄호 블록을 벗어나면 변수를 참조할 수 없게 된다.
ES6 이전에는 변수를 선언하기 위해 <code>var</code>키워드를 사용했다. 기존 <code>var</code>로 선언한 변수는 함수 스코프를 가지고 있었지만 <code>let</code>과 <code>const</code>로 선언한 변수나 상수는 블록 스코프를 가진다. 즉 중괄호 블록을 벗어나면 변수를 참조할 수 없게 된다.  
 
<source lang="js">
<syntaxhighlight lang="js">
{
{
   var a = 42
   var a = 42
23번째 줄: 22번째 줄:
}
}
console.log(b) // ReferenceError: b is not defined
console.log(b) // ReferenceError: b is not defined
</syntaxhighlight>
</source>


<code>const</code>로 선언하면 값을 변경할 수 없는 상수가 된다.
<code>const</code>로 선언하면 값을 변경할 수 없는 상수가 된다.
<syntaxhighlight lang="js">
<source lang="js">
const x = 0;
const x = 0;
x = 3; // 에러가 발생한다.
x = 3; // 에러가 발생한다.
</syntaxhighlight>
</source>


<code>var</code>로 선언한 변수와는 달리, <code>let</code>을 이용해 같은 스코프 내에 같은 이름의 변수를 두 번 만들면 에러를 발생시킨다. <code>var</code>로 선언한 변수를 <code>let</code>으로 다시 선언하거나, 그 반대의 경우에도 에러가 발생한다.
<code>var</code>로 선언한 변수와는 달리, <code>let</code>을 이용해 같은 스코프 내에 같은 이름의 변수를 두 번 만들면 에러를 발생시킨다. <code>var</code>로 선언한 변수를 <code>let</code>으로 다시 선언하거나, 그 반대의 경우에도 에러가 발생한다.
<syntaxhighlight lang="js">
<source lang="js">
var a = 3;
var a = 3;
var a = 7; // 문제 없음
var a = 7; // 문제 없음
41번째 줄: 40번째 줄:
let a = 3; // SyntaxError: redeclaration of var a
let a = 3; // SyntaxError: redeclaration of var a
var b = 3; // SyntaxError: redeclaration of let b
var b = 3; // SyntaxError: redeclaration of let b
</syntaxhighlight>
</source>


<code>let</code>을 이용해 for 반복문 내에서 클로저를 더 쉽게 사용할 수 있게 되었다. 다음 예제를 보자.
<code>let</code>을 이용해 for 반복문 내에서 클로저를 더 쉽게 사용할 수 있게 되었다. 다음 예제를 보자.
<syntaxhighlight lang="js">
<source lang="js">
var arr = [];
var arr = [];
for (var i = 0; i < 3; i++) {
for (var i = 0; i < 3; i++) {
52번째 줄: 51번째 줄:
arr[1](); // 3
arr[1](); // 3
arr[2](); // 3
arr[2](); // 3
</syntaxhighlight>
</source>
i를 출력하는 함수를 세 개 만들어 배열에 넣었는데, 결과는 모두 3을 출력하고 있다. 반복문이 끝나고 나면 i의 값이 3이 되기 때문이다. 반면 다음과 같이 <code>let</code>을 사용하면 좀 더 사용자가 원하는 동작을 볼 수 있다.
i를 출력하는 함수를 세 개 만들어 배열에 넣었는데, 결과는 모두 3을 출력하고 있다. 반복문이 끝나고 나면 i의 값이 3이 되기 때문이다. 반면 다음과 같이 <code>let</code>을 사용하면 좀 더 사용자가 원하는 동작을 볼 수 있다.


<syntaxhighlight lang="js">
<source lang="js">
var arr = [];
var arr = [];
for (let i = 0; i < 3; i++) {
for (let i = 0; i < 3; i++) {
63번째 줄: 62번째 줄:
arr[1](); // 1
arr[1](); // 1
arr[2](); // 2
arr[2](); // 2
</syntaxhighlight>
</source>
<code>let</code>을 이용해 변수를 선언시 매 반복마다 새 변수 i가 선언되기 때문에, 다음 반복이 일어나도 기존에 클로저에 캡쳐된 변수의 값이 변하지 않는다.
<code>let</code>을 이용해 변수를 선언시 매 반복마다 새 변수 i가 선언되기 때문에, 다음 반복이 일어나도 기존에 클로저에 캡쳐된 변수의 값이 변하지 않는다.
=== 화살표 함수 (Arrow function) ===
기존의 <code>function() {}</code>를 이용한 익명 함수 정의 대신 화살표 함수라고 부르는 새로운 함수 문법이 도입되었다.
<syntaxhighlight lang="js">
let fn1 = function(a, b) {
  let c = a + b
  return c
}
console.log(fn1(1, 2)) // 3
let fn2 = (a, b) => {
  let c = a + b
  return c
}
console.log(fn2(1, 2)) // 3
</syntaxhighlight>
위와 아래는 같은 역할을 한다. 다음과 같은 변형 문법도 존재한다.
<syntaxhighlight lang="js">
// 매개변수가 하나인 경우 괄호를 생략할 수 있다.
let fn1 = a => {
  let c = 2 * a
  return c
}
console.log(fn1(1)) // 2
// 간단한 식을 바로 리턴하려면 다음과 같이 블록과 return을 생략할 수 있다.
let fn2 = (a, b) => a + b
console.log(fn2(1, 2)) // 3
// 둘 다 적용하는 것도 가능하다.
let fn3 = obj => obj.name
console.log(fn3({ name: "aa" })) // "aa"
</syntaxhighlight>
문법이 약간 간단해진 것 외에, <code>this</code> 바인딩 문제를 쉽게 해결할 수 있다는 장점이 있다. <code>function</code>으로 정의한 함수는 호출시 <code>this</code>가 전역 객체(브라우저에선 <code>window</code>)가 된다. 객체의 메서드인 경우 <code>this</code>가 객체를 가리키고 있는데, 여기서 익명 함수를 정의하여 사용하고자 할 때 <code>this</code>가 원래 객체가 아니라 <code>window</code>가 되는 문제를 많은 사람들이 겪었다. 이를 해결하기 위해 원래 <code>this</code>를 <code>self</code> 등으로 옮겨 놓거나, <code>.bind</code>를 이용해 <code>this</code>를 바인딩해주는 등의 귀찮음이 있었다. 반면 화살표 함수로 함수를 정의할 경우, 정의할 당시의 <code>this</code>를 그대로 화살표 함수의 <code>this</code>로 사용하여 이런 문제가 없다.
=== 함수 기본 매개변수 ===
함수의 매개변수에 기본값을 지정해 줄 수 있게 되었다. 기존에는 매개변수로 넘어온 값이 <code>undefined</code>인지 일일히 검사하여 값을 할당했다.
<syntaxhighlight lang="js">
function f(x, y) {
  if (x === undefined) x = 3;
  if (y === undefined) y = 2;
  return x + y;
}
console.log(f()); // 5
</syntaxhighlight>
이제 매개변수 이름 뒤에 <code>= 기본값</code>을 붙여 기본값을 지정할 수 있다.
<syntaxhighlight lang="js">
function f(x = 3, y = 2) {
  return x + y;
}
console.log(f()); // 5
</syntaxhighlight>
=== 해체 할당 (Destructuring Assignment) ===
비구조화 할당, 구조 분해 할당 등등으로도 번역하는데, 딱 정해진게 없다. [[Python]]의 unpack과 같은 기능으로, 배열이나 객체의 구조를 분해해 그 안의 값들을 변수에 각각 할당하는 기능이다.
<syntaxhighlight lang="js">
const arr = [1, 2, 3];
let [x, y, z] = arr; // x, y, z에 각각 1, 2, 3이 대입된다.
console.log(x, y, z); // 1 2 3
</syntaxhighlight>
이를 이용해 두 변수의 값을 임시 변수 없이 바꿀 수 있다.
<syntaxhighlight lang="js">
let x = 3;
let y = 10;
[x, y] = [y, x];
console.log(x, y); // 10 3
</syntaxhighlight>
받고 싶지 않은 값은 생략할 수 있다. 또한, ...을 이용하면 남은 값을 새 배열로 받을 수도 있다.
<syntaxhighlight lang="js">
const arr = [1, 2, 3, 4, 5];
let [, y, z, ...rest] = arr; // y, z에 각각 2, 3이 대입된다. rest는 [4, 5]가 된다.
console.log(y, z); // 2 3
console.log(rest); // [4, 5]
</syntaxhighlight>
객체에도 적용할 수 있다. 할당 구문에서, <code>:</code>가 없으면 같은 이름의 프로퍼티를 가져와 대입하며, <code>:</code>가 있으면, 그 왼쪽과 같은 이름의 프로퍼티를 가져와서 오른쪽 이름의 변수에 대입한다.
<syntaxhighlight lang="js">
const obj = { x: 1, y: 2, z: 3 };
let { x, y, z: a } = obj; // 프로퍼티 x, y, z를 가져와서 변수 x, y, a에 각각 대입한다.
console.log(x, y, a); // 1 2 3
</syntaxhighlight>
<code>let</code>이나 <code>var</code>등의 변수 선언 키워드가 앞에 오지 않으면 문 전체를 소괄호<code>(...)</code>로 감싸줘야 한다. 그렇게 하지 않으면 해체 할당 문법이 아니라 블록 문법으로 해석해버리기 때문이다.
<syntaxhighlight lang="js">
const obj = { x: 1, y: 2, z: 3 };
let x, y, a;
({ x, y, z: a } = obj); // 프로퍼티 x, y, z를 가져와서 변수 x, y, a에 각각 대입한다.
console.log(x, y, a); // 1 2 3
</syntaxhighlight>
객체에서 <code>...</code>를 사용해 나머지를 새 객체에 할당하는 기능은 [[ECMAScript 2018]]에 표준화되었다.
=== class ===
자바스크립트도 [[Java]]나 [[C++]]와 같은 다른 많은 언어처럼 [[클래스]] 문법을 지원하게 되었다. 기존에는 클래스와 그 멤버를 정의하기 위해 다음과 같이 했다.
<syntaxhighlight lang="js">
function Person(name) {
  this.name = name;
}
Person.prototype.say = function() {
  console.log("My name is " + this.name + ".");
}
</syntaxhighlight>
이제 다음과 같이 작성할 수 있다.
<syntaxhighlight lang="js">
class Person {
  constructor(name) {
    this.name = name;
  }
  sayName() {
    console.log("My name is " + this.name + ".");
  }
}
const p = new Person("Kim");
p.sayName() // "My name is Kim."
</syntaxhighlight>
<code>extends</code>를 이용해 상속도 가능하다. 상속받은 클래스의 생성자에서는 <code>super()</code>를 이용해 부모 클래스의 생성자를 호출해야 한다.
<syntaxhighlight lang="js">
class Person {
  constructor(name) {
    this.name = name;
  }
  sayName() {
    console.log("My name is " + this.name + ".");
  }
}
class Student extends Person {
  constructor(name, score) {
    super(name);
    this.score = score;
  }
  sayScore() {
    console.log("My score is " + this.score + ".");
  }
}
const s = new Student("Kim", 80);
s.sayName() // "My name is Kim."
s.sayScore() // "My score is 80."
</syntaxhighlight>
=== Promise ===
비동기 처리를 위한 객체인 <code>Promise</code>가 추가되었다. 기존 자바스크립트에서는 비동기 처리를 위해 흔히 콜백(callback)함수를 이용했다. 그러나 콜백을 이용한 비동기 처리가 반복되면 계속 함수가 안쪽으로 중첩되어 가독성과 직관성이 떨어지는 문제가 생기게 된다. 이를 [[콜백 지옥]]이라고도 부른다. 또한 콜백 패턴에서의 에러 처리는 전통적인 방식인 <code>try...catch</code>를 적용할 수 없고 콜백의 인자에 넘겨줘야 하는데, 에러를 미처 처리하지 못하고 빼먹는 등 실수에 취약했다. 이러한 문제를 해결하기 위해 도입된 패턴이 Promise 패턴이다. ES6 이전에도 라이브러리를 통해 지원되어 사용하는 경우가 있었는데, ES6에서 이를 표준화한 것이다.
전통적인 비동기 처리 방식은 다음과 같았다. <code>asyncTask1</code>과 <code>asyncTask2</code>를 수행해서 그 반환값을 더해 출력하는 작업이다.
<syntaxhighlight lang="javascript">
asyncTask1(function (value1) {
    asyncTask2(function (value2) {
        console.log(value1 + value2);
    });
});
</syntaxhighlight>
비동기 작업을 하는 함수가 <code>Promise</code>를 지원한다면, 다음과 같이 코드를 쓸 수 있다.
<syntaxhighlight lang="javascript">
asyncTask1()
.then(() => asyncTask2())
.then(() => {
  console.log(value1 + value2);
});
</syntaxhighlight>
<code>Promise</code>를 리턴하는 함수를 실행하고 <code>.then()</code>에 다음 실행할 함수를 넣는다. <code>.then()</code>을 계속 매달아 그 다음 실행할 함수를 지정할 수 있다. 에러 처리는 <code>.catch()</code>로 한다.
<syntaxhighlight lang="javascript">
somethingAsync(value1)
  .then((result) => {
    // 성공시 수행할 작업
  })
  .catch((error) => {
    // 실패시 수행할 작업
  });
</syntaxhighlight>
기존 콜백 방식의 비동기 작업을 <code>Promise</code>로 바꾸려면, <code>new Promise()</code>를 이용해서 <code>Promise</code> 객체를 반환하면 된다. <code>new Promise()</code>의 인자에는 <code>(resolve, reject)</code>를 매개변수로 하는 함수를 넣어야 하는데, 그 안에서 기존 방식으로 비동기 함수를 실행하고, 성공시 <code>resolve</code>를, 실패시 <code>reject</code>를 실행하면 된다.
<syntaxhighlight lang="javascript">
function newAsyncFunction() {
  return new Promise((resolve, reject) => {
    oldAsyncFunction((error, ret) => {
      if (error) reject(error);
      else resolve(ret);
    });
  });
}
</syntaxhighlight>
[[ECMAScript 2017]]에서 표준으로 확정된 <code>async ... await</code>는 이 <code>Promise</code>를 이용한다. 자세한 사항은 [[ECMAScript 2017]]을 참조.


=== 컬렉션 ===
=== 컬렉션 ===
다른 객체들을 담기 위한 컬렉션 객체들이 추가되었다. <code>Map</code>, <code>Set</code>, <code>WeakMap</code>, <code>WeakSet</code>이 새로 추가된 객체들이다.
다른 객체들을 담기 위한 컬렉션 객체들이 추가되었다. <code>Map</code>, <code>Set</code>, <code>WeakSet</code>, <code>WeakMap</code>이 새로 추가된 객체들이다.


<code>Map</code>은 키-값 쌍(key-value pair)을 저장할 수 있는 컬렉션이다. 이미 자바스크립트의 객체(<code>{}</code>)는 키-값의 쌍을 저장하기는 하지만 객체의 기본 내장 메서드들과 이름이 충돌하는 문제가 있을 수 있고, 키가 문자열과 ES6에서 추가된 Symbol로만 한정되는 문제가 있다. <code>Map</code>은 데이터를 추가하거나 가져오는 메서드를 분리하여 데이터와 내장 메서드가 충돌하는 문제를 해결하였고, 문자열 뿐 아니라 숫자나 심지어 다른 객체를 키로 사용할 수 있다. <code>Map</code>은 다음과 같이 사용한다.
<code>Map</code>은 키-값 쌍(key-value pair)을 저장할 수 있는 컬렉션이다. 이미 자바스크립트의 객체(<code>{}</code>)는 키-값의 쌍을 저장하기는 하지만 객체의 기본 내장 메서드들과 이름이 충돌하는 문제가 있을 수 있고, 키가 문자열과 ES6에서 추가된 Symbol로만 한정되는 문제가 있다. <code>Map</code>은 데이터를 추가하거나 가져오는 메서드를 분리하여 데이터와 내장 메서드가 충돌하는 문제를 해결하였고, 문자열 뿐 아니라 숫자나 심지어 다른 객체를 키로 사용할 수 있다. <code>Map</code>은 다음과 같이 사용한다.
<syntaxhighlight lang="js">
<source lang="js">
let map = new Map();
let map = new Map();
map.set(300, "aa");
map.set(300, "aa");
let keyObject = { a: "b" };
let keyObject = { "a": "b" };
map.set(keyObject, "bb");
map.set(keyObject, "bb")
 
console.log(map.get(300)); // "aa"
console.log(map.get(keyObject)); // "bb"
map.set(keyObject, "cc");
console.log(map.get(keyObject)); // "cc", 같은 키를 두 번 사용하면 값을 덮어쓴다.
</syntaxhighlight>
 
<code>Set</code>은 [[집합 (추상 자료형)|집합]] 자료형을 구현한 것으로, 값들을 저장하되 중복 없이 딱 한 번씩만 저장한다. 값이 존재하는지 여부를 빠르게 검사할 수 있게 된다.
 
<syntaxhighlight lang="js">
let set = new Set();
set.add("aaaa");
console.log(set.has("aaaa")); // true
console.log(set.has("bbbb")); // false
set.add("aaaa"); // 이미 "aaaa"가 존재하므로 무시됨
</syntaxhighlight>
 
<code>Map</code>과 <code>Set</code> 모두 데이터를 넣은 순서를 그대로 유지하기 때문에, 다음과 같이 순회할 때 일정한 순서를 보장한다. Python의 dictionary와 set에는 이러한 보장이 없다.
 
<syntaxhighlight lang="js">
let set = new Set();
set.add("aaaa");
set.add("bbbb");
set.add("cccc");
for (let i of set) {
  console.log(i); // "aaaa", "bbbb", "cccc" 순서대로 출력
}
</syntaxhighlight>
 
V8의 구현에 따르면 <code>Map</code>과 <code>Set</code> 모두 해시를 이용해 시간복잡도 <math>O(1)</math>로 빠르게 데이터를 넣고 존재여부를 판단하거나 값을 가져올 수 있다.
 
<code>Map</code>, <code>Set</code>의 경우 키와 값에 대해 강한 레퍼런스를 가지고 있기 때문에, 키(<code>Set</code>의 경우에는 키=값으로 생각하면 된다)를 정의한 블록에서 벗어나 더는 그 키를 이용할 수 없게 되었더라도 <code>Map</code>이나 <code>Set</code>이 살아있는 한 키와 값이 계속 메모리를 차지하고 있게 된다. 즉, 메모리 누수의 위험이 항상 있다.
 
이러한 문제를 해결하기 위해 도입된 것이 <code>WeakMap</code>과 <code>WeakSet</code>이다. 이들은 각각 <code>Map</code>, <code>Set</code>과 같은 기능을 수행한다. 단 여기 사용된 키와 값은 가비지 컬렉션을 막지 않고, 가비지 컬렉션이 일어나면 사라지게 된다. 원래 객체의 수명 동안에만 필요한 추가 데이터를 저장한다든지 할 때 유용하게 사용할 수 있다. 단 <code>WeakMap</code>과 <code>WeakSet</code>은 넣은 객체들을 순회하거나, 가진 키-값 쌍을 모두 배열로 바꾸는 기능 등은 지원하지 않는다. 이 기능이 없기 때문에 키에 대한 레퍼런스를 잃어버리면 컬렉션에서도 접근할 수 없게 되어 안전하게 가비지 컬렉션을 수행할 수 있다.
 
<syntaxhighlight lang="js">
let map = new Map();
{
  let keyObject = { a: "b" };
  let valueObject = { b: "c" };
  map.set(keyObject, valueObject);
}
// 이 경우, 블록을 벗어나도 keyObject와 valueObject의 메모리는 반환되지 않는다.
 
let weakMap = new WeakMap();
{
  let keyObject = { a: "b" };
  let valueObject = { b: "c" };
  weakMap.set(keyObject, valueObject);
}
// 이 경우, 블록을 벗어나면 가비지 컬렉션이 수행될 때 안전하게 keyObject와 valueObject가 제거된다.
</syntaxhighlight>


[[분류:ECMAScript]]
console.log(map.get(300)) // "aa"
console.log(map.get(keyObject)) // "bb"
</source>
리브레 위키에서의 모든 기여는 크리에이티브 커먼즈 저작자표시-동일조건변경허락 3.0 라이선스로 배포됩니다(자세한 내용에 대해서는 리브레 위키:저작권 문서를 읽어주세요). 만약 여기에 동의하지 않는다면 문서를 저장하지 말아 주세요.
글이 직접 작성되었거나 호환되는 라이선스인지 확인해주세요. 리그베다 위키, 나무위키, 오리위키, 구스위키, 디시위키 및 CCL 미적용 사이트 등에서 글을 가져오실 때는 본인이 문서의 유일한 기여자여야 하고, 만약 본인이 문서의 유일한 기여자라는 증거가 없다면 그 문서는 불시에 삭제될 수 있습니다.
취소 편집 도움말 (새 창에서 열림)

| () [] [[]] {{}} {{{}}} · <!-- --> · [[분류:]] · [[파일:]] · [[미디어:]] · #넘겨주기 [[]] · {{ㅊ|}} · <onlyinclude></onlyinclude> · <includeonly></includeonly> · <noinclude></noinclude> · <br /> · <ref></ref> · {{각주}} · {|class="wikitable" · |- · rowspan=""| · colspan=""| · |}

이 문서에서 사용한 틀: