링크 : https://d2.naver.com/helloworld/7495331
"2016년과 이후 JavaScript의 동향"과 "2017년과 이후 JavaScript의 동향 - JavaScript(ECMAScript)"에서 JavaScript의 현재를 살펴보고 미래를 예측해 봤습니다. "2017년과 이후 JavaScript의 동향 - JavaScript(ECMAScript)"가 게시된 이후 2017년은 2016년에 비해 상대적으로 조용한 해였다고 할 수 있습니다. 그렇다고 해서 실제로 조용한 것은 아니었고 계속해서 JavaScript가 발전하고 성숙해지는 모습을 보여 주었다고 할 수 있습니다. ECMAScript 2015 발표 이후 해마다 표준 명세가 발표됐고, 브라우저는 표준 명세를 빠르게(명세가 발표되기 전에라도) 구현했습니다. 그래서 '명세'로만 존재하는 것이 아니라 바로 '사용'이 가능한 기술의 범위가 점점 확대되고 있습니다.
이 글에서는 ECMAScript 2017의 주요 기능을 살펴보고, 현재 제안된 기능에서 다음 명세인 ECMAScript 2018에 포함될 것으로 예상되는 기능을 살펴보겠습니다. 그리고 앞으로 중요성이 점점 증가할 것으로 보이는 WebAssembly도 간략하게 살펴보겠습니다.
2017년의 JavaScript와 표준 명세
JavaScript의 변화라는 측면에서 2017년은 2016년에 비해 상대적으로 조용한 해였다고 할 수 있다. 하지만 브라우저가 표준 명세를 빠르게 구현해 브라우저의 ECMAScript 2015(이하 ES6) 지원율이 증가했으며, 표준 명세의 발표 주기도 일 년으로 변경됐다. 이제 기능의 지원 여부를 명세의 버전으로 확인하는 방법은 의미가 없어지고 있다.
브라우저의 ECMAScript 2015 지원율 증가
"2017년과 이후 JavaScript의 동향 - JavaScript(ECMAScript)"에서 2017년에 ES6의 사용이 폭발적으로 늘어날 것이라고 예측한 대로 이제 ES6 이후 명세는 JavaScript 개발에서 당연하게 사용해야 하는 명세로 자리를 잡았다.
ES6 호환성 테이블(ECMAScript 6 compatibility table)(2018년 3월 기준)에서는 최신 데스크톱 브라우저와 모바일 브라우저의 ES6 지원율이 90% 이상이다. 이는 사용자가 업데이트하지 않아도 자동으로 업데이트되는 브라우저('에버그린 브라우저'라고 부른다)의 사용자를 대상으로 개발할 때는 별도의 트랜스파일 과정이 필요 없음을 의미한다.
네이티브 모듈 지원
"2017년과 이후 JavaScript의 동향 - JavaScript(ECMAScript)"에서 ES6의 모듈을 소개할 때는 모듈을 사용하기 위해 트랜스파일러가 필요했다. 그 이후 얼마나 많은 진전이 있었을까?
아직 Firefox에서는 별도로 dom.moduleScripts.enabled
속성을 true
로 설정해야 하지만("Experimental features in Firefox"의 'JavaScript' 참고) 2017년부터는 대다수의 최신 브라우저에서 네이티브 모듈을 사용할 수 있게 됐다.
브라우저에서 네이티브 모듈을 사용하는 방법에 관해서는 "ECMAScript modules in browsers"를 참고한다.
그러나 곧 완료될 것으로 예측했던 동적 모듈 로딩과 import() 구문은 아직도 논의 단계에 머물러 있어 브라우저 구현까지 좀 더 시간이 필요할 것으로 보인다.
버전 구분의 중요성 감소
ECMAScript 다섯 번째 명세인 'ECMAScript 5'(이하 ES5)의 다음 여섯 번째 명세는 'ECMAScript 2015'로 공개됐다. 명세의 버전을 공개 연도를 사용해 표기하면서 이후 JavaScript 명세의 릴리스 방식과 주기가 기존의 단일 릴리스에서 일 년 단위 릴리스(yearly release)로 변경됐다.
릴리스 방식과 주기를 변경한 가장 큰 이유는 다음 2가지다.
- 새로운 명세의 릴리스까지 시간이 너무 오래 소요된다. 1999년에 공개된 ECMAScript 3의 다음 명세인 ES5가 공개된 2009년까지 10년이 걸렸다. ES5의 다음 명세인 ES6도 6년 후인 2015년에 공개됐다.
- 단일 릴리스 모델에는 한계가 있다. 단일 릴리스 모델에서는 릴리스 전에 이미 승인이 완료된 기능을 명세가 공식적으로 릴리스될 때까지 기다려야 하는 문제가 있다. 또한 논의에 시간이 오래 걸리는 기능의 검토에 압박감을 갖게 된다. 해당 기능이 다음 릴리스로 미루어지면 다음 릴리스가 공개될 때까지 또 오랜 시간을 기다려야 하기 때문이다. 또한 논의가 길어지는 기능이 전체 명세의 릴리스를 지연시키는 요인으로 지목되기도 한다.
JavaScript 표준 명세를 논의하는 TC39 기술위원회가 채택한 릴리스 방식에서는 제안된 모든 기능에 대해 표준 승인까지 총 5단계로 진행되는 논의 절차를 거치고 Stage 4(Finished 단계)에 도달한 기능이 포함된 명세를 해마다 발표한다.
기능이 Stage 4에 도달했다고 100% 표준 명세에 포함된다고는 할 수 없다. 최종적으로는 표준 명세의 릴리스에 포함됐을 때 승인이 '완료'됐다고 할 수 있다.
변경된 릴리스 방식으로 인해 ECMAScript 명세의 버전보다는 제안된 기능이 표준 승인의 어떤 단계에 도달했는지가 더 중요해지고 있다. 물론 현재도 'ECMAScript 2015'와 'ECMAScript 2017' 같이 버전을 표시하지만 기능의 지원 여부를 버전을 통해 확인하는 방법은 점점 의미가 없어져 갈 수밖에 없다.
표준 명세의 이름도 불필요하게 혼란스럽다고 생각한다.
'ECMAScript' 또는 'JavaScript'라 부르면서도 표준 명세를 지칭할 땐 'ECMAScript'(줄여서 'ES')를 사용한다. 하지만 표준 명세의 버전을 표시할 때는 몇 번째 표준 명세인지 번호를 사용하기도 하고 표준 명세를 공개한 연도를 사용하기도 한다. 표준 명세의 이름과 의미를 간략하게 정리한 글에서도 이런 혼란스러움에 대한 불만이 보인다.
ECMAScript 2017의 주요 기능
2017년 1월에 진행된 TC39 회의에서 ECMAScript의 여덟 번째 표준 명세에 포함될 기능이 결정됐고, 2017년 6월에 'ECMAScript 2017'(이하 ES8)로 최종 표준 명세가 발표됐다.
ES8에 포함된 주요 기능은 다음과 같다.
async/await 함수
async/await 함수는 이미 Chrome 55 버전에서 구현됐다("New In Chrome 55"의 'async and await' 참고). 그리고 현재 주요 데스크톱 브라우저와 모바일 브라우저에서 네이티브 함수로 사용할 수 있는 상태다(브라우저의 async 함수 지원표 참고).
Generator 객체 기반의 플로 컨트롤 라이브러리인 co에 영감을 받아 제안된 async/await 함수는 코드를 동기식으로 읽은 다음 비동기식으로 실행할 수 있게 한다. 비동기 처리로 인한 콜백 문제(callback hell)를 해결하기 위해 이미 ES6에서 Promise 객체가 등장했다. 하지만 async/await 함수의 문법은 보다 직관적이고 단순해 개발자가 익숙한 기존의 동기식 코드 형식으로 비동기식 처리 코드를 작성할 수 있다.
다음은 Promise 객체와 async/await 함수로 작성한 비동기식 처리 코드의 예다.
// Promise 객체
function getPersonInfo(id) {
return fetch(id)
.then(info => info.text())
.then(text => JSON.parse(text))
.catch(err => {{
console.log(`Error: ${err.message}`);
});
}
// async/await 함수
async function getPersonInfo(id) {
try {
const info = await fetch(id);
const text = await info.text();
return JSON.parse(text);
} catch (err) {
console.log(`Error: ${err.message}`);
}
}
SharedArrayBuffer 객체와 Atomics 객체를 사용한 메모리 공유
멀티 스레드를 활용한 성능 향상은 꾸준히 시도됐다("ES proposal: Shared memory and atomics"의 'A history of JS parallelism' 참고).
비동기 콜백 호출을 통해 브라우저가 별도의 스레드에서 작업을 처리하거나 Web Workers를 사용해 각각의 worker가 개별 스레드에서 병렬적으로 실행될 수 있게 했지만 많은 제약(메인 스레드와 worker 스레드의 제한적인 통신, DOM 직접 접근 제한 등)이 있었다. 그 외에 WebGL을 사용해 GPU를 활용하는 방법이나 SIMD.js(Single Instruction, Multiple Data)가 있었다. 그리고 Firefox에 적용됐다가 많은 관심을 끌어내지 못하고 결국 2015년에 사라진 PJS(Parallel JavaScript, 코드명은 River Trail)도 있다(Bugzilla 이슈 참고).
SIMD.js
SIMD.js는 저수준 데이터의 병렬 처리를 위한 명세로 제안됐다. 하지만 WebAssembly를 위한 SIMD 제안이 진행되면서 SIMD.js 제안은 Stage 3(Candidate 단계)에서 제거됐다.
ES8에서는 SharedArrayBuffer 객체와 Atomics 객체를 사용해 메모리를 공유하는 기능이 추가됐다.
SharedArrayBuffer 객체
SharedArrayBuffer 객체는 길이가 고정된(fixed-length) 바이너리 데이터 버퍼다. ArrayBuffer 객체와 유사하지만 공유된 메모리에 뷰를 생성할 때 사용할 수 있다.
웹에서 작업을 병렬적으로 실행하는 기본적인 방법은 Web Workers를 사용하는 것이다. 그러나 worker는 각자 분리된 전역 환경에서 실행되기 때문에 worker(또는 메인 스레드) 간 통신을 통한 간접적인 형태 외에는 데이터가 직접 공유되지 않았다.
SharedArrayBuffer 객체는 에이전트(메일 스레드, worker 등)가 데이터를 공유할 수 있게 한다.
// 1024바이트 크기의 버퍼 생성
var sab = new SharedArrayBuffer(1024);
// worker를 통해 다른 에이전트와 데이터 공유
worker.postMessage(sab);
SharedArrayBuffer 객체와 CPU 보안
CPU의 설계 오류로 인한 보안 취약점인 Meltdown과 Spectre는 2018년 초에 큰 보안 이슈가 됐다. 이 보안 취약점으로 인해 브라우저에서도 웹 콘텐츠에 포함될 수 있는 민감한 사용자 데이터(서로 다른 출처(origin) 간의 데이터 포함)가 노출될 가능성이 있다고 알려졌다.
이에 따라 대부분의 브라우저 제조사는 2018년 1월 5일을 기점으로 관련 패치가 적용되는 시점까지 SharedArrayBuffer 객체의 사용을 임시로 중단했다(이 글을 읽는 시점에 따라 SharedArrayBuffer 객체의 사용이 재개됐을 수도 있다).
브라우저 제조사의 대응에 관한 자세한 내용은 다음 글을 참고한다.
- Chromium: Actions required to mitigate Speculative Side-Channel Attack techniques
- Mozilla: Mitigations landing for new class of timing attack
- Microsoft: Mitigating speculative execution side-channel attacks in Microsoft Edge and Internet Explorer
- Webkit: What Spectre and Meltdown Mean For WebKit
Atomics 객체
Atomics 객체는 아토믹 연산(atomic operation)을 위한 정적 메서드를 제공하며, SharedArrayBuffer 객체와 같이 사용된다.
SharedArrayBuffer 객체를 사용해 에이전트 간에 데이터를 공유할 때 각 에이전트는 아무 때라도 각자의 메모리에 읽기와 쓰기 등의 작업을 실행할 수 있다. 이 경우 에이전트 간의 작업(다른 에이전트의 쓰기 작업이 끝날 때까지 대기하는 작업)을 어떻게 온전하고 질서 있게 관리할 수 있을까? Atomics 객체를 사용해서 관리한다.
에이전트는 다른 에이전트의 데이터 쓰기 작업이 끝날 때까지 대기 큐에 포함되며 'sleep' 상태로 기다린다. Atomics.wait() 메서드는 '깨어'나는 조건이 발생할 때까지 대기하게 하고, 데이터 읽기가 필요한 경우 Atomics.load() 메서드로 특정 지점의 값을 읽어 들일 수 있다.
// 1024바이트 크기의 버퍼 생성
var sab = new SharedArrayBuffer(1024);
// shared typedArray 객체 생성
var int32 = new Int32Array(sab);
// 읽기 스레드가 typedArray 객체의 인덱스 번호가 '0'인 위치의 값이 '0'인지 테스트한다. 'true'가 반환되면 'sleep' 상태에서 대기한다.
// 대기 타임아웃을 지정할 수 있다(기본값: Infinity).
Atomics.wait(int32, 0, 0);
// 쓰기 스레드가 새로운 값을 저장하면 깨어나 새로운 값을 반환한다.
console.log(int32[0]); // 123
SharedArrayBuffer 객체와 Atomics 객체에 관한 더 자세한 내용은 다음의 시리즈 글을 참고한다.
- A crash course in memory management(한글 번역)
- A cartoon intro to ArrayBuffers and SharedArrayBuffers(한글 번역)
- Avoiding race conditions in SharedArrayBuffers with Atomics(한글 번역)
그 외 추가 기능
Stage 4에 도달한 제안(2018년 3월 기준)에서 ES8에 포함된 주요 제안을 간략하게 살펴보면 다음과 같다.
- Object.values/Object.entries: 객체의 값과 요소를 쉽게 추출할 수 있는 기능을 제공한다.
- String padding: 문자열의 시작과 끝에 문자열을 덧붙일 수 있는 기능을 제공한다.
- Object.getOwnPropertyDescriptors: 객체 자신의 속성(own property)에 대한 설명자를 모두 반환하는 기능을 제공한다. 이와 비교해 Object.getOwnPropertyDescriptor() 메서드는 객체 자신의 특정 속성에 대한 설명자를 반환한다.
- Trailing commas in function parameter lists and calls: 배열 정의, 객체 정의, 함수 정의, 메서드 정의, 함수 호출 등에서 나열되는 마지막 요소에 콤마(
,
)를 사용할 수 있게 한다. JavaScript는 이미 배열에서 마지막 콤마(trailing comma)를 허용했다. 이후 ES5에서는 객체를 정의할 때도 마지막 콤마를 허용하게 됐다. ES8에서는 함수의 파라미터에서도 마지막 콤마를 허용한다. 마지막 콤마를 사용하면 개발자가 코드 작성할 때 찾기 어려운 문법 오류를 일으키는 일을 줄일 수 있으며, 새로운 항목을 추가할 때 이전 항목에 콤마를 별도로 추가하는 수고를 덜어 준다. 다음은 마지막 콤마를 사용하는 코드의 예다.
// 배열 정의 시
var arr = [
1,
2,
3,
];
// 객체 정의 시
var obj = {
name: "John Doe",
sex: "M",
age: 43,
};
// 함수 정의 시
function f(p,) {}
(p,) => {};
// 메서드 정의 시
var obj = {
two(a, b,) {},
};
// 함수 호출 시
f(p,);
Math.max(10, 20,);
ECMAScript 2017의 브라우저 구현
ECMAScript 2016 이후 명세의 브라우저 호환성 테이블(ECMAScript 2016+ compatibility table)(2018년 3월 기준)에서는, Firefox와 Microsoft Edge가 지원하지 않는 일부 항목이 있지만, 명세 항목의 대부분을 데스크톱 브라우저와 모바일 브라우저가 지원하고 있는 것으로 나타난다("SharedArrayBuffer 객체와 Atomics 객체를 사용한 메모리 공유"의 'SharedArrayBuffer 객체와 CPU 보안'에서 언급한 것처럼 CPU 보안 이슈로 'shared memory and atomics' 항목의 지원은 잠시 중단됐다).
Android 4 버전(Ice Cream Sandwich)부터 Chrome이 Android의 기본 브라우저로 설정됐다. Android 5 버전(Lollipop)부터 개별적인 웹 엔진 업데이트가 가능해짐에 따라 모바일에서 Android 버전을 구분하는 의미가 사라졌다("Add android 6-8 to the table" 참고).
ECMAScript 2018의 주요 기능
이전 명세의 발표 일정을 고려했을 때 ES8의 다음 공식 명세인 ECMAScript 2018(이하 ES9)은 아마 2018년 6월에 발표될 것이다. ES9에 포함될 항목은 Stage 4에 도달한 제안(2018년 3월 기준)을 살펴보면 쉽게 예측할 수 있다.
ECMAScript 2018에 포함될 주요 기능 예상
Stage 4에 도달한 제안에서 ES9에 포함될 것으로 예상하는 주요 제안은 다음과 같다. 특히 정규 표현식과 관련된 새로운 제안이 많이 포함될 것으로 보인다.
이 글에서 예상한 제안에 관한 더 자세한 설명과 예는 "What's New in ES2018?"을 참고한다.
- Asynchronous Iteration: 비동기 스트림 데이터를 열거할 수 있는 기능을 제공한다.
- Rest/Spread Properties: 펼침(spread) 속성과 나머지(rest) 속성을 객체에서 사용할 수 있는 기능을 제공한다.
- RegExp named capture groups: 캡처 그룹에 이름을 지정할 수 있는 기능을 제공한다.
(?<이름>패턴)
과 같이<이름>
을 캡처 그룹 패턴에 추가하고, 이후 지정한 이름을 사용해 캡처를 참조할 수 있게 한다.
const rx = /(?<year>[0-9]{4})-(?<month>[0-9]{2})/;
const match = rx.exec('2018-03');
match.groups.year; // 2018
match.groups.month; // 03
- RegExp Unicode Property Escapes: 정규 표현식의 패턴에는 이스케이프 문자(
\
)로 시작되는 특수 문자를 사용할 수 있다. 예를 들어\s
는 공백을 의미한다. 이 특수 문자를\p{유니코드_문자_속성_값}
형태와 같이\p
특수 문자와 유니코드 문자 속성 값으로 표현할 수 있는 기능을 제공한다.
> /^\p{White_Space}+$/u.test('\t \n\r'); // true
> /^\p{Script=Greek}+$/u.test('μετά'); // true
- RegExp Lookbehind Assertions: 정규 표현식에서는 특정 패턴의 값이 뒤따르거나(lookahead) 뒤따르지 않는(negative lookahead) 문자열을 탐색한다. 이 제안은 반대로 특정 패턴의 값이 앞서거나(lookbehind) 앞서지 않는(negative lookbehind) 문자열을 탐색할 수 있는 기능을 제공한다.
// positive lookahead
/aa(?=bb)/.test("aabb"); // true
// negative lookahead
/aa(?!bb)/.test("aac"); // true
// positive lookbehind
/(?<=aa)bb/.test("aabb"); // true
// negative lookbehind
/(?<!=aa)bb/.test("bbbb"); // true
s
(dotAll
) flag for regular expressions: 마침표(.
)는 모든 문자와 매칭되지만 줄바꿈 문자(\r
,\n
)와는 매칭되지 않았다. 이 문제를 해결하기 위해 새로운 플래그s
를 지정해 줄바꿈 문자와 매칭될 수 있게 하는 기능을 제공한다.
// 기존
/./test("a"); // true
/./.test("\n"); // false
// dotAll 플래그
/./s.test("\n"); // true
그 외 추가 제안
그 외에 다음 제안이 포함될 것으로 예측된다.
- Promise.prototype.finally: try...catch 구문의 finally 구문과 비슷하게 Promise 객체에서 finally 구문을 사용할 수 있는 기능을 제공한다.
- Lifting template literal restriction: 템플릿 리터럴에서 이스케이프 시퀀스 사용에 대한 제약을 제거하는 제안이다.
WebAssembly
WebAssembly(wasm)는 별도의 플러그인을 사용하지 않고 브라우저에서 네이티브 언어 수준의 성능을 보여 줄 수 있는 기술을 목표로 한다. WebAssembly의 사용 환경이 2017년에 어떻게 진행됐는지 간략하게 살펴보고, WebAssembly를 간접적으로 사용해 볼 수 있는 도구를 소개하겠다.
WebAssembly에 관한 더 자세한 내용은 "WebAssembly"를 참고한다.
WebAssembly 사용 환경
"2017년과 이후 JavaScript의 동향 - JavaScript(ECMAScript)"에서 2017년에는 주요 브라우저가 WebAssembly를 지원할 것이라 예측한 대로 현재 모든 브라우저에서 WebAssembly를 사용할 수 있는 상태가 됐다. 브라우저뿐만 아니라 Node.js도 8 버전부터 WebAssembly를 지원하기 시작했고("Using WebAssembly With NodeJS" 참고), 번들링 도구인 webpack도 2018년 2월 25일에 발표된 4.0 버전부터 WebAssembly 모듈을 실험적으로 지원한다.
아직 온전하게 본궤도에 올랐다고 보기에는 어렵지만 차츰 WebAssembly의 사용이 증가할 것으로 예측된다. WebAssembly를 가장 잘 활용할 수 있는 영역인 게임에서 WebAssembly를 지원하기 시작했다는 점이 이런 예측을 강력히 뒷받침한다. 언리얼 엔진 4의 Epic 게임에서 WebAssembly를 지원하고("Mozilla and Epic Preview Unreal Engine 4 Running in Firefox" 참고), Unity에서도 WebAssembly를 지원한다("유니티 5 최종 버전, 5.6 출시" 참고).
WebAssembly 도구
WebAssembly를 지금 당장 사용하기는 어려울 수 있다. 간접적으로 WebAssembly를 사용해 볼 수 있는 도구를 몇 가지 소개하겠다.
- Emscripten: 네이티브 코드로 작성된 기존의 코드를 손쉽게 WebAssembly 코드로 전환하는 LLVM-to-JavaScript 컴파일러 프로젝트다.
- WebAssembly Explorer: C/C++ 컴파일러를 통해 어떻게 WebAssembly 코드가 생성되고 브라우저에서 실행되는지를 온라인에서 확인해 볼 수 있다.
- WasmFiddleWebAssembly Fiddle: JavaScript 코드 조각(snippet)을 온라인에서 직접 작성하고 테스트할 수 있는 서비스인 JSFiddle, JS Bin, CodePen 등과 유사하게 WebAssembly의 코드 조각을 실행해 볼 수 있는 도구다.
- WebAssembly Code ExplorerWebAssembly binaries: 바이너리 수준에서 코드가 어떻게 인코딩되는지 살펴볼 수 있다.
마치며
과거는 물론 지금도 새로운 명세를 사용하려면 외부 도구의 힘을 빌려야 한다. 하지만 네이티브 방식으로 지원하는 기능이 늘어나고 지원 속도도 점차 빨라지고 있다. 그에 따라 앞으로는 새로운 명세를 사용할 때 별도의 변환(transpile) 과정이 점점 줄어들 수밖에 없을 것으로 예측된다("You might not need to transpile your JavaScript" 참고).
주목을 받는 기술의 등장도 중요하지만 꾸준하고 지속적인 발전과 유지가 오히려 더 어렵다고 생각한다. 별다른 문제가 없다면 매년 새롭게 릴리스되는 명세와 함께 JavaScript는 더 발전하고 계속해서 영향력을 키워나갈 것으로 보인다.
다음 글에서는 대표적인 프런트엔드 라이브러리의 동향을 다루겠다.
2018년과 이후 JavaScript의 동향
'WEB > 기타' 카테고리의 다른 글
Java 유료 논쟁, Oracle JDK와 OpenJDK의 차이 정리 (0) | 2019.02.18 |
---|---|
MS, 엣지브라우저 크로미엄으로 재개발 공식화 (0) | 2019.01.22 |
네이버페이 JavaScript SDK 개발기 (0) | 2019.01.22 |
네이버 메인 페이지의 트래픽 처리 (0) | 2019.01.22 |
GIF대신 Video로 변환해보자 (0) | 2019.01.22 |