최신판 |
당신의 편집 |
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> |