ECMAScript Proposals - ES2016 & ES2017

tc39에서 진행하고 있는 ECMAScript의 다음 버전들 및 그 후보들을 알아본다.
그 중 본 글에서는 Stage 4(ES2016 및 ES2017에 새로 도입되기로 확정된 기능들)을 살펴보겠다.

 

ECMAScript Proposals?

ECMAScript2015(ES6)가 출범한지 어느덧 1년 반 여의 세월이 흘렀다.
그 사이 ECMAScript2016가 릴리즈 되었고(2016. 6.), 내년 중반에는 ES2017이 릴리즈될 예정이다.

ECMAScript2015는 기존 ES3, ES5의 흐름에 따라 ES6로 부르기도 했으나, ECMAScript2016부터는 ‘해마다 표준이 추가됨’을 강조하고자 ES 뒤에 해당 년도를 붙인 것만을 정식 명칭으로 하기로 결정했다고 한다. 사실 ES6와 ES2015, ES7와 ES2016 등을 매칭하기엔 끝의 숫자가 하나씩 달라서 헷갈리던 참이었는데, 본 글을 읽는 독자분들께서도 정식 명칭을 사용하시면 원활한 의사소통을 위해서도 좋을 것 같다.

javascript의 추가 제안사항들을 회의와 테스트를 거쳐 선별, 해마다 javascript 표준안을 정하는 단체인 tc39는, 각 제안 내용을 0 ~ 4 단계로 분류하고 있다.

Stage description
0 Strawman
1 Proposal
2 Draft
3 Candidate
4 Finished

참고 : 2ality - The TC39 process for ECMAScript features

앞으로 연재 형태로 4단계부터 1단계까지에 걸쳐 제안된 기능들을 살펴보고자 한다.
본 글에서는 우선 Stage 4(ES2016 표준 및 ES2017 도입 예정안)를 살펴보겠다.


 

Stage 4

표준안에 추가될 것이 결정된 내용. 이 단계의 제안들은 잠정적으로 다음 년도 ECMAScript 표준안에 도입될 것이나, 경우에 따라 연기될 가능성도 존재한다.
이미 ECMAScript 2016는 릴리즈된 상태이며, ECMAScript 2017에 도입될 내용도 거의 결정되었다. ES2016의 경우 대부분의 최신 모던브라우저에 기능 구현이 되어 있다.

(ES2016에 도입된 내용들이 아직도 Stage 4에 표기되어 있는 이유는… 단순히 업데이트를 안한 것일까?)

 

[2016] Array.prototype.includes

Array.prototype.includes

기존에는 배열 요소 중에 어떤 값이 있는지 여부를 확인하기 위해 다음과 같은 방식을 이용해왔다.

1
2
3
if (arr.indexOf(el) !== -1) {
...
}

이 방식은

  • 의미론적으로 와닿지 않는 방식이고,
  • NaN을 제대로 판별할 수 없는 문제가 있다([NaN].indexOf(NaN) === -1).

이에 Array.prototype.includes 메소드가 ES2016 최종스펙에 도입되었다.

1
Array.prototype.includes(value)
1
2
3
4
5
6
7
8
9
10
console.log([1, 2, 3].includes(2)) // true
console.log([1, 2, 3].includes(4)) // false

console.log([1, 2, NaN].includes(NaN)) // true

console.log([1, 2, -0].includes(+0)) // true
console.log([1, 2, +0].includes(-0)) // true

console.log(['a', 'b', 'c'].includes('a')) // true
console.log(['a', 'b', 'c'].includes('a', 1)) // false

 

[2016] Exponentiation Operator **

Exponentiation Operator

다른 프로그래밍 언어들에서 일반적으로 사용되는 문법을 도입하였다.
x ** y는 x의 y제곱을 의미하며, 이는 Math.pow(x, y)와 완전히 동일하다.

1
number ** number
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
console.log(Math.pow(2, 3) === 2 ** 3) // true
console.log(2 ** 3) // 8 ( === 2 * 2 * 2 )

let a = 3
a **= 4
console.log(a) // 81 ( === a * a * a * a )

10 ** -1 // 0.1

2.5 ** 2 // 6.25
3 ** 2.5 // 15.588457268119896

2 ** (3 ** 2) // 512
2 **
((3 ** 2)(
// 512
2 ** 3,
) **
2) // 64

 

[2017] Object.values / Object.entries

Object.values / Object.entries

ES2015의 Map, Set, Array 등에는 [Map/Set/Array].prototype.keys, [Map/Set/Array].prototype.values, [Map/Set/Array].prototype.entries의 메소드가 있으며,
이 메소드들은 각각 이터레이터를 반환한다.
한편 Object에는 Object.keys 라는 스태틱 메소드(ES5)만이 존재하며, 결과는 이터레이터가 아닌 배열을 반환한다.

ES2015에서 추가된 타 데이터타입의 메소드들과의 형평성을 맞추면서 기존 ES5 문법과의 통일성을 유지하기 위해, Object에는 스태틱 메소드로 valuesentries를 추가할 예정이다.
각각 값으로만 구성된 배열, [키, 값]의 쌍으로 구성된 2차원배열을 반환한다.

1
2
Object.values(object)
Object.entries(obj)
1
2
3
4
const obj = { a: 1, b: 2, c: 3 }
console.log(Object.keys(obj)) // [ "a", "b", "c" ]
console.log(Object.values(obj)) // [ 1, 2, 3 ]
console.log(Object.entries(obj)) // [ ["a", 1], ["b", 2], ["c", 3] ]

 

[2017] String padding

String.prototype.padStart / String.prototype.padEnd

최대 길이보다 짧은 문자열에 대해서 그 여백에 지정한 문자열을 반복하여 채우는 메소드이다.
padStart는 문자열의 좌측에 여백을 지정하며, padEnd는 그 반대이다.
두 메소드 모두 maxLength보다 긴 문자열에 대해서는 동작하지 않는다.

1
2
String.prototype.padStart(maxLength[, padString])
String.prototype.padEnd(maxLength[, padString])
1
2
3
4
5
6
7
8
9
'abc'.padStart(10) // "       abc"  (두번째 파라미터 생략시 빈 문자열로 채운다)
'abc'.padStart(10, '12') // "1212121abc"
'abc'.padStart(5, '1234567') // "12abc"
'abcde'.padStart(3, '12') // "abcde"

'abc'.padEnd(10) // "abc " (두번째 파라미터 생략시 빈 문자열로 채운다)
'abc'.padEnd(10, '12') // "abc1212121"
'abc'.padEnd(5, '1234567') // "abc12"
'abcde'.padEnd(3, '12') // "abcde"

 

[2017] Object.getOwnPropertyDescriptors

Object.getOwnPropertyDescriptors

객체의 프로퍼티 속성들을 기술한 객체를 반환한다. Object.defineProperties의 활용도를 높이는 계기가 될 것으로 예상한다.

1
Object.getOwnPropertyDescriptors(obj)
1
2
3
4
5
6
7
8
const res = Object.getOwnPropertyDescriptors({ a: 1 })
console.log(res.a)
// {
// configurable: true,
// enumerable: true,
// value: 1,
// writable: true
// }

Object.assign을 이용하면 원본 객체의 getter / setter 가 제대로 복사되지 않는 문제가 있었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = {
get a() {
return this.b
},
set a(v) {
console.log(v)
},
}
obj.a = 10 // 10 출력
console.log(obj.a) // undefined

const obj2 = Object.assign({}, obj)
obj2.a = 20 // 출력 없음
console.log(obj2.a) // 20

Object.getOwnPropertyDescriptors를 이용하면 제대로 복사가 이뤄질 수 있다.

1
2
3
const obj3 = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj))
obj3.a = 30 // 30 출력
console.log(obj3.a) // undefined

참고 : 2ality - ES proposal: Object.getOwnPropertyDescriptors()

 

[2017] Trailing commas in function parameter lists and calls

Proposal to allow trailing commas in function parameter lists

함수 파라미터들 중 마지막 값 뒤에 찍힌 콤마를 오류로 잡지 않게 된다.

1
2
3
4
5
function n(a, b) {
console.log(a, b)
}

n('a', 'b')

이를 활용하면 다음과 같이 버전관리 도구 등에서 바뀐 내용을 보다 명확하게 확인할 수 있게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 이전 버전
function x(a, b) {
console.log(a, b)
}
x(10, 20)

// 변경된 버전
function x(
a,
b, // 변경사항 표시되지 않음.
c, // 새로 추가되었음이 표시됨.
) {
console.log(a, b, c) // 변경사항 표시됨.
}
x(
10,
20, // 변경사항 표시되지 않음.
30, // 새로 추가되었음이 표시됨.
)

 

[2017] Async function

Async Function

비동기 데이터처리를 간단한 방식으로 구현할 수 있게 된다.

1
2
3
4
5
6
7
8
9
10
async function fetchJson(url) {
// 'async' 명령어로 비동기함수임을 명시.
try {
let request = await fetch(url) // fetch의 결과가 반환될 때까지 대기('await').
let text = await request.text() // request.text() 값이 반환될 때 비로소 진행.
return JSON.parse(text)
} catch (error) {
console.log(`ERROR: ${error.stack}`)
}
}

표기법은 다음과 같다.

  • 비동기 함수 선언문 : async function foo() {}
  • 비동기 함수 표현식 : const foo = async function () {}
  • 비동기 메소드 정의 : let obj = { async foo() {} }
  • 비동기 화살표함수 정의 : const foo = async () => {}

참고: 2ality - async functions